Cara Menulis Skrip CGI Perl CGI yang lebih aman. Panduan dengan beberapa tips.
Pembaruan Maret 2020 : Migrasi tutorial ini dari 2008 ke GitHub untuk tujuan arsip. (Saat ini ada bahasa pemrograman yang lebih baik dan kerangka kerja web).
Panduan ini berisi beberapa contoh skrip CGI yang menunjukkan cara meningkatkan keamanan dalam skrip CGI. Contoh Count7.CGI memiliki keamanan terbaik.
Jika Anda menulis skrip CGI Anda di Perl, Anda harus
Gunakan bendera -T . Ini juga merupakan ide yang baik untuk menggunakan bendera -w . Skrip karenanya akan dimulai dengan
#!/usr/bin/perl -Tw
Hindari menggunakan file sementara sebanyak mungkin. Jika Anda ingin menelepon program eksternal, coba gunakan panggilan open2() . Ini berfungsi dengan asumsi bahwa program eksternal dapat membaca data input dari input standar dan menulis hasilnya ke output standar. Misalnya, seperti ini
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);
Jangan pernah menggunakan sesuatu seperti ini
system("myprogram -f $cgiparam");
Alih -alih menggunakan sintaksis yang terpisah koma
system("myprogram","-f","$cgiparam");
Count.py adalah skrip Python yang digunakan oleh semua contoh skrip 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)
Mengkonfigurasi Apache HTTPD untuk memungkinkan sisi server termasuk (SSI) berbahaya. Pilihannya disebut Includes .
Seorang peretas dapat mengunggah file dengan pernyataan SSI Exec. Perintah -perintah itu kemudian akan dieksekusi segera setelah browser web mencoba mengakses file yang diunggah. Seringkali SSI hanya diaktifkan untuk nama file yang diakhiri dengan " .shtml ". Dalam skrip CGI ini, peselancar web memilih nama file dan dapat memilih nama file yang diakhiri dengan " .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); . Jika Anda tidak dapat menghindari menggunakan file sementara, silakan gunakan modul Perl ini untuk membuatnya. Meskipun tidak memungkinkan di sini, Anda harus lebih disukai menggunakan UNLINK => 1 sebagai argumen untuk fungsi 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;
}
Seringkali dimungkinkan untuk menghindari file sementara dengan menggunakan pipa. Di Perl ini dilakukan dengan 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;
}
Contoh jumlah sebelumnya count3.cgi tidak aman karena melewati variabel $type sebagai langsung ke baris perintah (ke shell). Kami sebaliknya harus menggunakan sintaksis yang terpisah dari open2() seperti yang ditunjukkan di sini:
#!/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;
}
Contoh mode noda, bendera -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;
}
Jika kami mencoba menjalankan contoh sebelumnya. CGI , Perl akan mengeluh bahwa variabel PATH tidak aman. Jika kita menggunakan bendera -T , kita perlu mengatur variabel lingkungan 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;
}
Jika kami mencoba menjalankan contoh sebelumnya. CGI Perl akan mengeluh bahwa kami menggunakan variabel yang tidak ternoda dengan cara yang tidak aman. Untuk menggunakan variabel input CGI, kita perlu mencabutnya dengan memprosesnya dengan ekspresi reguler
#!/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;
}