كيفية كتابة برامج نصية بيرل CGI أكثر أمانًا. دليل مع بعض النصائح.
تحديث مارس 2020 : قم بترحيل هذا البرنامج التعليمي من عام 2008 إلى GitHub لأغراض الأرشيف. (في الوقت الحاضر ، هناك لغات برمجة أفضل وأطر الويب).
يحتوي هذا الدليل على بعض البرامج النصية CGI التي توضح كيفية تحسين الأمان في البرامج النصية CGI. مثال العد 7.cgi لديه أفضل أمان.
إذا كنت تكتب البرامج النصية CGI الخاصة بك في Perl ، فيجب عليك
استخدم علامة -T . من الجيد أيضًا استخدام علامة -w . سيبدأ البرنامج النصي مع
#!/usr/bin/perl -Tw
تجنب استخدام الملفات المؤقتة قدر الإمكان. إذا كنت ترغب في الاتصال ببرنامج خارجي ، فحاول استخدام مكالمة open2() . يعمل هذا على افتراض أن البرنامج الخارجي يمكنه قراءة بيانات الإدخال من الإدخال القياسي وكتابة النتيجة إلى الإخراج القياسي. على سبيل المثال ، مثل هذا
use IPC::Open2;
my $childpid = open2(*READ, *WRITE, $config::blastall_path , "-m", "7", "-d" , $db, "-p", "blastp", "-b", $max_hits, "-v", $max_hits, "-M", "BLOSUM62" )
or die "Could not open pipe: $!";
print WRITE $blastinput;
close(WRITE);
my $blastoutput;
while(<READ>) {
$blastoutput .= $_;
}
close READ;
waitpid($childpid, 0);
لا تستخدم شيئًا كهذا
system("myprogram -f $cgiparam");
بدلاً من ذلك ، استخدم بناء الجملة المنفصل
system("myprogram","-f","$cgiparam");
Count.py هو نص Python الذي يستخدمه كل نصوص CGI على سبيل المثال.
#!/usr/bin/python
import sys
if len(sys.argv) == 2:
sys.exit()
elif len(sys.argv) == 3:
f=open(sys.argv[2], "r")
else:
sys.exit()
#f.seek(0)
lines=0
bytes=0
for line in f:
lines += 1
bytes += len( line )
if (sys.argv[1] == "bytes"):
print str(bytes)
if (sys.argv[1] == "lines"):
print str(lines)
يعد تكوين Apache HTTPD للسماح لجانب الخادم (SSI) خطيرًا. يسمى الخيار Includes .
يمكن للمتسلل تحميل ملف مع عبارات SSI Exec. سيتم بعد ذلك تنفيذ هذه الأوامر بمجرد أن يحاول متصفح الويب الوصول إلى الملف الذي تم تحميله. غالبًا ما يتم تنشيط SSI فقط لأسماء الملفات التي تنتهي بـ " .shtml ". في هذا البرنامج النصي CGI ، يختار Web Surfer اسم الملف ويمكنه اختيار اسم ملف ينتهي بـ " .shtml ".
#!/usr/bin/perl
use CGI qw(:standard);
use CGI::Carp qw(fatalsToBrowser);
my $cgi = new CGI;
if ($cgi->cgi_error()) { die() };
print
$cgi->header(),
$cgi->start_html( '' ),
$cgi->h1('Count'),
$cgi->start_form(-method=>'GET'),
"type:",
$cgi->popup_menu(-name=>'type',-values=>['lines','bytes']),
$cgi->p,
"filename",
$cgi->textfield(-name=>'fname'),
$cgi->p,
$cgi->textarea(-name=>'content', -rows=>10, -columns=>50),
$cgi->p,
$cgi->submit(),
$cgi->end_form,
$cgi->hr;
if ($cgi->param) {
my $fname = $cgi->param('fname');
my $content = $cgi->param('content');
my $type = $cgi->param('type');
my $filename="tmp/" . $fname;
my $outfh;
open($outfh, ">", $filename)
or die "Couldn't open $tmpfile for writing: $!";
print $outfh $content;
close $outfh;
my $result=`/home/esjolund/public_html/cgi-bin/count.py $type $filename`;
print "The file <b>$fname</b> has $result $type";
print $cgi->end_html;
}
examplistes use File::Temp qw(tempfile); . إذا لم تتمكن من تجنب استخدام الملفات المؤقتة ، فيرجى استخدام وحدة Perl هذه لإنشاءها. على الرغم من أنه غير ممكن هنا ، إلا أنه يجب أن تستخدم UNLINK => 1 كوسيطة لوظيفة tempfile() .
#!/usr/bin/perl
use CGI qw(:standard);
use CGI::Carp qw(fatalsToBrowser);
use File::Temp qw(tempfile);
my $cgi = new CGI;
if ($cgi->cgi_error()) { die() };
print
$cgi->header(),
$cgi->start_html( '' ),
$cgi->h1('Count'),
$cgi->start_form(-method=>'GET'),
"type:",
$cgi->popup_menu(-name=>'type',-values=>['lines','bytes']),
$cgi->p,
$cgi->textarea(-name=>'content', -rows=>10, -columns=>50),
$cgi->p,
$cgi->submit(),
$cgi->end_form,
$cgi->hr;
if ($cgi->param) {
my $content = $cgi->param('content');
my $type = $cgi->param('type');
File::Temp->safe_level( File::Temp::HIGH );
my ( $outfh, $filename ) =
tempfile( "tmp/myfiles.XXXXXX", UNLINK => 0 );
if ( !defined $outfh ) {
die "error: no temporary filen";
}
print $outfh $content;
close $outfh;
my $result=`/home/esjolund/public_html/cgi-bin/count.py $type $filename`;
print "Content has $result $type";
print $cgi->end_html;
}
غالبًا ما يكون من الممكن تجنب الملفات المؤقتة باستخدام الأنابيب. في بيرل يتم ذلك باستخدام open2()
#!/usr/bin/perl
use CGI qw(:standard);
use CGI::Carp qw(fatalsToBrowser);
use IPC::Open2;
my $cgi = new CGI;
if ($cgi->cgi_error()) { die() };
print
$cgi->header(),
$cgi->start_html( '' ),
$cgi->h1('Count'),
$cgi->start_form(-method=>'GET'),
"type:",
$cgi->popup_menu(-name=>'type',-values=>['lines','bytes']),
$cgi->p,
$cgi->textarea(-name=>'content', -rows=>10, -columns=>50),
$cgi->p,
$cgi->submit(),
$cgi->end_form,
$cgi->hr;
if ($cgi->param) {
my $content = $cgi->param('content');
my $type = $cgi->param('type');
my($child_out, $child_in);
$pid = open2($child_out, $child_in, "/home/esjolund/public_html/cgi-bin/count.py", $type,"/dev/stdin");
print $child_in $content;
close($child_in);
my $result=<$child_out>;
waitpid($pid,0);
print "Content has $result $type";
print $cgi->end_html;
}
المثال السابق count3.cgi غير آمن لأنه يمرر متغير $type كما هو الحال مباشرة إلى سطر الأوامر (إلى shell). يجب علينا بدلاً من ذلك استخدام بناء جملة الفاصلة المنفصل لـ open2() كما هو موضح هنا:
#!/usr/bin/perl
use CGI qw(:standard);
use CGI::Carp qw(fatalsToBrowser);
use IPC::Open2;
my $cgi = new CGI;
if ($cgi->cgi_error()) { die() };
print
$cgi->header(),
$cgi->start_html( '' ),
$cgi->h1('Count'),
$cgi->start_form(-method=>'GET'),
"type:",
$cgi->popup_menu(-name=>'type',-values=>['lines','bytes']),
$cgi->p,
$cgi->textarea(-name=>'content', -rows=>10, -columns=>50),
$cgi->p,
$cgi->submit(),
$cgi->end_form,
$cgi->hr;
if ($cgi->param) {
my $content = $cgi->param('content');
my $type = $cgi->param('type');
my($child_out, $child_in);
$pid = open2($child_out, $child_in, "/home/esjolund/public_html/cgi-bin/count.py",$type);
print $child_in $content;
close($child_in);
my $result=<$child_out>;
waitpid($pid,0);
print "Content has $result $type";
print $cgi->end_html;
}
يقوم بامتيازات وضع taint ، علامة -T
#!/usr/bin/perl -T
use CGI qw(:standard);
use CGI::Carp qw(fatalsToBrowser);
use IPC::Open2;
use Scalar::Util qw(tainted);
my $cgi = new CGI;
if ($cgi->cgi_error()) { die() };
print
$cgi->header(),
$cgi->start_html( '' ),
$cgi->h1('Count'),
$cgi->start_form(-method=>'GET'),
"type:",
$cgi->popup_menu(-name=>'type',-values=>['lines','bytes']),
$cgi->p,
$cgi->textarea(-name=>'content', -rows=>10, -columns=>50),
$cgi->p,
$cgi->submit(),
$cgi->end_form,
$cgi->hr;
if ($cgi->param) {
if (tainted($cgi->param('content')))
{ print 'variable content tainted!'; }
else
{ print 'variable content not tainted!'; }
print `echo $cgi->param('content')`;
my $content = $cgi->param('content');
my $type = $cgi->param('type');
my($child_out, $child_in);
$pid = open2($child_out, $child_in, "/home/esjolund/public_html/cgi-bin/count.py",$type);
print $child_in $content;
close($child_in);
my $result=<$child_out>;
waitpid($pid,0);
print "Content has $result $type";
print $cgi->end_html;
}
إذا حاولنا تشغيل المثال السابق count5.cgi ، فسيشكو Perl من أن متغير PATH غير آمن. إذا استخدمنا علامة -T ، فنحن بحاجة إلى ضبط متغير بيئة PATH .
#!/usr/bin/perl -T
use CGI qw(:standard);
use CGI::Carp qw(fatalsToBrowser);
use IPC::Open2;
use Scalar::Util qw(tainted);
my $cgi = new CGI;
if ($cgi->cgi_error()) { die() };
$ENV{'PATH'} = '/bin:/usr/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
print
$cgi->header(),
$cgi->start_html( '' ),
$cgi->h1('Count'),
$cgi->start_form(-method=>'GET'),
"type:",
$cgi->popup_menu(-name=>'type',-values=>['lines','bytes']),
$cgi->p,
$cgi->textarea(-name=>'content', -rows=>10, -columns=>50),
$cgi->p,
$cgi->submit(),
$cgi->end_form,
$cgi->hr;
if ($cgi->param) {
my $content = $cgi->param('content');
my $type = $cgi->param('type');
if ( tainted($type) )
{ print 'tainted!'; }
else
{ print 'not tainted!'; }
print $cgi->hr;
print `echo $type`;
my($child_out, $child_in);
$pid = open2($child_out, $child_in, "/home/esjolund/public_html/cgi-bin/count.py",$type);
print $child_in $content;
close($child_in);
my $result=<$child_out>;
waitpid($pid,0);
print "Content has $result $type";
print $cgi->end_html;
}
إذا حاولنا تشغيل المثال السابق count6.CGI Perl سوف يشكو من أننا نستخدم متغيرات غير ملوثة بطريقة غير آمنة. لاستخدام متغيرات إدخال CGI ، نحتاج إلى إلغاءورها عن طريق معالجتها بتعبير منتظم
#!/usr/bin/perl -T
use CGI qw(:standard);
use CGI::Carp qw(fatalsToBrowser);
use IPC::Open2;
use Scalar::Util qw(tainted);
my $cgi = new CGI;
if ($cgi->cgi_error()) { die() };
$ENV{'PATH'} = '/bin:/usr/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
print
$cgi->header(),
$cgi->start_html( '' ),
$cgi->h1('Count'),
$cgi->start_form(-method=>'GET'),
"type:",
$cgi->popup_menu(-name=>'type',-values=>['lines','bytes']),
$cgi->p,
$cgi->textarea(-name=>'content', -rows=>10, -columns=>50),
$cgi->p,
$cgi->submit(),
$cgi->end_form,
$cgi->hr;
if ($cgi->param) {
my $type;
if ( $cgi->param('type')=~/^(bytes|lines)$/ ) {
$type = $1;
} else {
print "variable type needs to be bytes or lines";
exit(0);
}
my $content;
if ( $cgi->param('content')=~/^(.*)$/s ) {
$content = $1;
} else {
exit(0);
}
if ( tainted($type) )
{ print 'tainted!'; }
else
{ print 'not tainted!'; }
print $cgi->hr;
print `echo $type`;
my($child_out, $child_in);
$pid = open2($child_out, $child_in, "/home/esjolund/public_html/cgi-bin/count.py",$type);
print $child_in $content;
close($child_in);
my $result=<$child_out>;
waitpid($pid,0);
print "Content has $result $type";
print $cgi->end_html;
}