Comment rédiger des scripts Perl CGI plus sécurisés. Un guide avec quelques conseils.
Mise à jour mars 2020 : Migrez ce tutoriel de 2008 vers GitHub à des fins d'archives. (De nos jours, il existe de meilleurs langages de programmation et des cadres Web).
Ce guide contient quelques exemples de scripts CGI qui montrent comment améliorer la sécurité dans les scripts CGI. L'exemple Count7.cgi a la meilleure sécurité.
Si vous écrivez vos scripts CGI à Perl, vous devriez
Utilisez le drapeau -T . C'est aussi une bonne idée d'utiliser le drapeau -w . Le script commencera donc par
#!/usr/bin/perl -Tw
Évitez d'utiliser autant que possible les fichiers temporaires. Si vous souhaitez appeler un programme externe, essayez d'utiliser l'appel open2() . Cela fonctionne en supposant que le programme externe peut lire les données d'entrée de l'entrée standard et écrire le résultat à la sortie standard. Par exemple, comme ça
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);
N'utilisez jamais quelque chose comme ça
system("myprogram -f $cgiparam");
Utilisez plutôt la syntaxe séparée par la virgule
system("myprogram","-f","$cgiparam");
Count.py est un script Python qui est utilisé par tous les exemples de scripts 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)
La configuration d' Apache HTTPD pour permettre au côté serveur inclut (SSI) est dangereuse. L'option est appelée Includes .
Un pirate pourrait télécharger un fichier avec des instructions d'execs SSI. Ces commandes seraient ensuite exécutées dès qu'un navigateur Web essaie d'accéder au fichier téléchargé. Souvent SSI n'est activé que pour les noms de fichiers se terminant par " .shtml ". Dans ce script CGI, le surfeur Web choisit le nom de fichier et peut choisir un nom de fichier se terminant par " .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;
}
Examplifie use File::Temp qw(tempfile); . Si vous ne pouvez pas éviter d'utiliser des fichiers temporaires, veuillez utiliser ce module Perl pour les créer. Bien que non possible ici, vous devez préfère utiliser UNLINK => 1 comme argument à la fonction 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;
}
Il est souvent possible d'éviter les fichiers temporaires en utilisant des tuyaux. Dans Perl, cela se fait avec 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;
}
L'exemple précédent count3.cgi n'est pas en sécurité car il passe la variable $type comme directement à la ligne de commande (à un shell). Nous devons plutôt utiliser la syntaxe séparée par virgule d' open2() comme indiqué ici:
#!/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;
}
Examplifie le mode de souillure, l'indicateur -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;
}
Si nous essayons d'exécuter l'exemple précédent Count5.cgi , Perl se plaindra que la variable PATH n'est pas en sécurité. Si nous utilisons l'indicateur -T , nous devons définir la variable d'environnement 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;
}
Si nous essayons d'exécuter l'exemple précédent Count6.cgi Perl se plaindra que nous utilisons des variables intactes de manière non sécurisée. Pour utiliser les variables d'entrée CGI, nous devons les intratignons en les traitant avec une expression régulière
#!/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;
}