Как написать более безопасные сценарии Perl CGI. Руководство с некоторыми советами.
Обновление марта 2020 года : перенести этот урок с 2008 года в GitHub для архивных целей. (В настоящее время есть лучшие языки программирования и веб -структуры).
Это руководство содержит некоторые примеры сценариев CGI, которые демонстрируют, как повысить безопасность в сценариях CGI. Пример count7.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 веб -серфер выбирает имя файла и может выбрать имя файла, заканчивающее " .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;
}
Examplifies 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;
}
Часто можно избежать временных файлов с помощью труб. В Perl это делается с 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 , как непосредственно в командную строку (в оболочку). Вместо этого мы должны использовать разделенный запятой синтаксис 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;
}