Lorsqu'un client envoie une requête pour obtenir un fichier HTML, le
serveur lui retourne la page demandée (ou un message d'erreur). Le
navigateur interprète alors le code HTML pour le mettre en forme et
l'afficher. Par exemple, avec l'URL (Uniform Request Locator)
www.linuxdoc.org/HOWTO/HOWTO-INDEX/howtos.html
, le client
se connecte au serveur www.linuxdoc.org
et lui demande
la page /HOWTO/HOWTO-INDEX/howtos.html
, le dialogue ayant
lieu en employant le protocole HTTP. Si cette page
existe, le serveur renvoie le contenu du fichier demandé. Dans ce
modèle, qualifié de statique, si le fichier est présent sur
le serveur, il est transmis tel quel au client, sinon un message
d'erreur (le fameux 404 - Not Found) est renvoyé.
Malheureusement, ce schéma ne permet pas d'interactions avec un
utilisateur, ce qui rend impossible les opérations comme le
e-commerce, la e-réservation de ses vacances ou encore le e-n'importe
quoi.
Heureusement, il existe des solutions pour générer dynamiquement des
pages HTML. Les scripts CGI (Common Gateway Interface) sont l'une
d'elles. A la différence du cas
précédent, les URLs pour y accéder sont construites légèrement
différemment :
http://<serveur><chemin vers le script>[?[param_1=val_1][...][¶m_n=val_n]]La liste d'arguments est stockée dans la variable d'environnement
QUERY_STRING
.
Un script CGI, dans ce contexte, n'est rien d'autre qu'un fichier
exécutable. Il utilise l'entrée standard stdin
pour
récupérer les arguments qui lui sont passés. Après exécution de son
code, il affiche son résultat sur la sortie standard
stdout
, qui est alors redirigée vers le client
web. Pratiquement n'importe quel langage de programmation convient
pour écrire un script CGI (programme C compilé, Perl, shell-scripts...).
www.linuxdoc.org
savent de ssh :
http://www.linuxdoc.org/cgi-bin/ldpsrch.cgi?svr=http%3A%2F%2Fwww.linuxdoc.org&srch=ssh&db=1&scope=0&rpt=20En fait, ceci est beaucoup plus simple qu'il n'y paraît. Décomposons cette URL :
www.linuxdoc.org
;
/cgi-bin/ldpsrch.cgi
;
?
signale le début d'une longue liste
d'arguments :
srv=http%3A%2F%2Fwww.linuxdoc.org
est le
serveur d'où est émise la requête ;
srch=ssh
contient la requête à proprement
parler ;
db=1
signifie que la requête ne porte que sur
les HOWTOs ;
scope=0
stipule que la recherche concerne
également le contenu du document, et pas seulement son
titre ;
rpt=20
limite à 20 le nombre de résultats
affichés.
SSI Server Side Include
"
Server Side Include
est une fonction intégrée
aux serveurs web. Il permet d'intégrer des directives dans les pages
web, soit pour inclure un fichier tel quel, soit pour exécuter une
commande (shell ou script CGI).
Dans le fichier de configuration httpd.conf
d'Apache,
l'instruction "AddHandler server-parsed .shtml
" active ce
mécanisme. Souvent, afin d'éviter le distinguo entre
.html
et .shtml
, on y ajoute également
l'extension .html
. Évidemment, cela ralentit un peu le serveur...
Cette possibilité est contrôlée au niveau des répertoires par les
directives :
Options Includes
active tous les SSI ;
OptionsIncludesNoExec
interdit le exec
cmd
et le exec cgi
.
guestbook.cgi
ci-joint, le texte
fourni par l'utilisateur est directement inclus dans un fichier HTML,
sans conversion des caractères '<' et ' >' en code html < et
>.
Il suffit alors à une personne curieuse de soumettre une des
instructions suivantes :
<!--#printenv -->
(attention à l'espace
après printenv
)
<!--#exec cmd="cat /etc/passwd"-->
guestbook.cgi?email=pappy&texte=<%21--%23printenv%20-->
,
quelques informations sur le système sont révélées :
DOCUMENT_ROOT=/home/web/sites/www8080 HTTP_ACCEPT=image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */* HTTP_ACCEPT_CHARSET=iso-8859-1,*,utf-8 HTTP_ACCEPT_ENCODING=gzip HTTP_ACCEPT_LANGUAGE=en, fr HTTP_CONNECTION=Keep-Alive HTTP_HOST=www.esiea.fr:8080 HTTP_PRAGMA=no-cache HTTP_REFERER=http://www.esiea.fr:8080/~grenier/cgi/guestbook.cgi?email=&texte=%3C%21--%23include+file%3D%22guestbook.cgi%22--%3E HTTP_USER_AGENT=Mozilla/4.76 [fr] (X11; U; Linux 2.2.16 i686) PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin REMOTE_ADDR=194.57.201.103 REMOTE_HOST=nef.esiea.fr REMOTE_PORT=3672 SCRIPT_FILENAME=/mnt/c/nef/neftiles/grenier/public_html/cgi/guestbook.html SERVER_ADDR=194.57.201.103 SERVER_ADMIN=master8080@cgsecurity.org SERVER_NAME=www.esiea.fr SERVER_PORT=8080 SERVER_SIGNATURE=<ADDRESS>Apache/1.3.14 Server at www.esiea.fr Port 8080</ADDRESS> SERVER_SOFTWARE=Apache/1.3.14 (Unix) (Red-Hat/Linux) PHP/3.0.18 GATEWAY_INTERFACE=CGI/1.1 SERVER_PROTOCOL=HTTP/1.0 REQUEST_METHOD=GET QUERY_STRING= REQUEST_URI=/~grenier/cgi/guestbook.html SCRIPT_NAME=/~grenier/cgi/guestbook.html DATE_LOCAL=Tuesday, 27-Feb-2001 15:33:56 CET DATE_GMT=Tuesday, 27-Feb-2001 14:33:56 GMT LAST_MODIFIED=Tuesday, 27-Feb-2001 15:28:05 CET DOCUMENT_URI=/~grenier/cgi/guestbook.shtml DOCUMENT_PATH_INFO= USER_NAME=grenier DOCUMENT_NAME=guestbook.shtml
exec
, elle donne pratiquement
l'équivalent d'un shell :
guestbook.cgi?email=pappy&texte=%3c%21--%23exec%20cmd="cat%20/etc/passwd"%20--%3e
.
<!--#include
file="/etc/passwd"-->
", le
chemin est relatif au répertoire où se trouve le fichier HTML et il ne
peut contenir de "..
". Le fichier error_log
d'Apache contient alors un message signalant une tentative d'accès à
un fichier interdit. L'utilisateur voit dans la page html le message
[an error occurred while processing this directive]
.
Dans cette partie, nous présentons des failles de sécurité liées à
l'emploi de Perl pour écrire des scripts CGI. Afin de tenter de
conserver une certaine lisibilité, nous ne fournissons pas le code
complet des exemples, seulement les extraits nécessaires pour
comprendre où se situe le problème.
Tous nos scripts sont construits sur le modèle suivant :
#!/usr/bin/perl -wT BEGIN { $ENV{PATH} = '/usr/bin:/bin' } delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Make %ENV safer =:-) print "Content-type: text/html\n\n"; print "<HTML>\n<HEAD><TITLE>Remote Command</TITLE></HEAD>\n"; &ReadParse(*input); # #################################### # # Début du code descriptif du problème # # #################################### # # ################################## # # Fin du code descriptif du problème # # ################################## # form: print "<form action=\"$ENV{'SCRIPT_NAME'}\">\n<input type=texte name=filename>\n </form>\n"; print "</BODY>\n"; print "</HTML>"; exit(0); sub ReadParse { local (*in) = @_ if @_; my ($i, $key, $val); my $in_first; my @in_second; # Read in text if (&MethGet) { $in_first = $ENV{'QUERY_STRING'}; } elsif ($ENV{'REQUEST_METHOD'} eq "POST") { read(STDIN,$in_first,$ENV{'CONTENT_LENGTH'}); } @in_second = split(/&/,$in_first); foreach $i (0 .. $#in_second) { # Convert plus's to spaces $in_second[$i] =~ s/\+/ /g; # Split into key and value. ($key, $val) = split(/=/,$in_second[$i],2); # splits on the first =. # Convert %XX from hex numbers to alphanumeric $key =~ s/%(..)/pack("c",hex($1))/ge; $val =~ s/%(..)/pack("c",hex($1))/ge; # Associate key and value $in{$key} .= "\0" if (defined($in{$key})); # \0 is the multiple separator $in{$key} .= $val; } return length($#in_second); } sub MethGet { return ($ENV{'REQUEST_METHOD'} eq "GET"); }
Nous reviendrons ultérieurement sur les arguments passés à Perl
(-wT
). Nous commençons par nettoyer nos variables
d'environnement $ENV
et $PATH
, puis nous
envoyons l'entête du fichier HTML. La fonction
ReadParse()
permet de récupérer les arguments passés au
script. Des modules permettent de faire ceci bien plus souplement,
mais pour vous éviter de devoir les installer si ce n'est déjà fait
sur votre serveur web, cette fonction suffit amplement.
Ensuite, les exemples présentés ci-dessous prennent place. Enfin, nous
concluons notre fichier HTML.
Perl considère tous les caractères de manières identiques, ce qui
n'est pas le cas des fonctions C par exemple. Le caractère nul de fin
de chaîne est, pour Perl, un caractère comme les autres. Et alors ?
Ajoutons le code suivant à notre script pour obtenir showhtml.cgi
:
# showhtml.cgi my $filename= $input{filename}.".html"; print "<BODY>File : $filename<BR>"; if (-e $filename) { open(FILE,"$filename") || goto form; print <FILE>; }
ReadParse()
récupère l'unique argument
transmis : le nom du fichier à afficher. Pour éviter qu'un petit
comique essaye de lire autre chose que les fichiers HTML, nous ajoutons
l'extension ".html
" à la fin du nom du fichier. Mais
rappelez-vous que le caractère nul est un caractère comme les
autres ...
showhtml.cgi?filename=%2Fetc%2Fpasswd%00
,
le fichier s'appelle alors my $filename =
"/etc/passwd\0.html"
et nos yeux ébahis contemple un fichier
qui n'est pas du HTML.
strace
nous montre bien le
cheminement suivi lors de l'ouverture d'un fichier en Perl :
/tmp >>cat >open.pl << EOF > #!/usr/bin/perl > open(FILE, "/etc/passwd\0.html"); > EOF /tmp >>chmod 0700 open.pl /tmp >>strace ./open.pl 2>&1 | grep open execve("./open.pl", ["./open.pl"], [/* 24 vars */]) = 0 ... open("./open.pl", O_RDONLY) = 3 read(3, "#!/usr/bin/perl\n\nopen(FILE, \"/et"..., 4096) = 51 open("/etc/passwd", O_RDONLY) = 3
open()
affiché par strace
correspond à l'appel-système, écrit en C. On constate que l'extension
.html
a disparu, ce qui provoque bien l'ouverture du
fichier protégé.
s/\0//g
Voici maintenant le cas d'un script sans aucune protection qui affiche le fichier de votre choix sous une certaine arborescence :
#pipe1.cgi my $filename= "/home/httpd/".$input{filename}; print "<BODY>File : $filename<BR>"; open(FILE,"$filename") || goto form; print <FILE>;
$nom_utilisateur
,
$nom_societe
...) qui sont définis dans le programme
Perl. J'ai trouvé un bogue similaire dans un script où
le fichier template était passé en argument.
pipe1.cgi?filename=../../../etc/passwd
open(FILE, "/bin/ls")
ouvre le fichier
binaire "/bin/ls
"... mais
open(FILE, "/bin/ls |")
exécute la commande
spécifiée. L'ajout d'un simple tube
'|
change le comportement du open()
.
pipe1.cgi?filename=..%2F..%2F..%2Fbin%2Fcat%20%2fetc%2fpasswd%20|
affiche encore le contenu du fichier de mots de passe.
#pipe2.cgi my $filename= "/home/httpd/".$input{filename}; print "<BODY>File : $filename<BR>"; if (-e $filename) { open(FILE,"$filename") || goto form; print <FILE> } else { print "-e failed: no file\n"; }L'exemple précédent ne fonctionne plus. En effet, le test d'existence "
-e
échoue car il ne trouve pas le fichier
"../../../bin/cat /etc/passwd |
".
/bin/ls
. Le comportement
sera la même qu'auparavant. En effet, si nous cherchons, par
exemple, à lister le contenu du répertoire /etc
, le
"-e
" teste alors l'existence
du fichier "../../../bin/ls /etc |
, mais il n'existe pas
plus. Tant que nous ne fournirons pas le nom d'un fichier "fantôme",
nous ne pourrons rien tirer d'intéressant :(
/bin/ls
existe bien lui (enfin,
sur la plupart des systèmes), mais si le open()
est
appelé juste avec ce nom de fichier, la commande ne sera pas exécutée
mais le binaire sera affiché. Il faut donc trouver une solution pour
placer un tube '|
' à la fin du nom, sans que ce
'|
ne soit utilisé dans la vérification provoquée par le
"-e
". Nous connaissons déjà la solution : l'octet
nul. Si nous transmettons "../../../bin/ls\0|
" comme nom,
le test d'existence réussi car il ne portera que sur
"../../../bin/ls
" mais le open()
verra bien
le pipe et exécutera la commande. Ainsi, l'URL fournit le contenu du
répertoire courant :
pipe2.cgi?filename=../../../bin/ls%00|
Le script finger.cgi
(source) exécute l'instruction
finger
sur notre machine :
#finger.cgi print "<BODY>"; $login = $input{'login'}; $login =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"])/\\$1/g; print "Login $login<BR>\n"; print "Finger<BR>\n"; $CMD= "/usr/bin/finger $login|"; open(FILE,"$CMD") || goto form; print <FILE>
\
' devant afin d'en
empêcher l'interprétation. Ainsi, le point-virgule est transformé en
"\;
par l'expression régulière. Mais la liste ne contient
pas tous les caractères importants. Il manque, entre autre, le retour
à la ligne '\n
.
RETURN
ou
ENTER
, ce qui provoque l'envoi d'un caractère
'\n
'. En Perl, le même mécanisme est possible. Nous avons
déjà vu que l'instruction open()
nous laissait exécuter
une commande sous réserve de terminer la ligne par un tube
'|
'.
finger.cgi?login=kmaster%0Acat%20/etc/passwd
&&
: si la première instruction réussi
(i.e.
renvoie 0 en shell), alors la suivante est exécutée ;
||
: si la première instruction échoue
(i.e.
renvoie une valeur non nulle en shell), alors la suivante est
exécutée.
Le script finger.cgi
précédent prend des mesures pour
éviter de se voir passer des caractères étranges. Ainsi, l'URL
finger.cgi?login=kmaster;cat%20/etc/passwd
ne fonctionne pas car le point-virgule est échappé.
Cependant, il est un caractère qui n'est pas très souvent
protégé : le backslash '\
'.
Par exemple, prenons le cas d'un script qui veut interdire de remonter
dans une arborescence en utilisant le répertoire
"..
", l'expression régulière s/\.\.//g
efface les "..
", mais ça ne sert à rien car les shells
supportent parfaitement l'amoncellement de plusieurs '/
'
(essayez cat ///etc//////passwd
pour vous en
convaincre).
Par exemple, dans le script pipe2.cgi
ci-dessus, la
variable $filename
est initialisée à partir du préfixe
"/home/httpd/
". L'utilisation de l'expression régulière
précédente pourrait sembler efficace pour empêcher de remonter dans
les répertoires. Certes, cette expression protège de
"..
", mais que se passe-t-il si on ruse un peu en
protégeant nous-mêmes le caractère '.
' ? En effet,
l'expression régulière ne correspond pas si le nom du fichier est
.\./.\./etc/passwd
. Signalons que ceci fonctionne très
bien avec system()
(ou les guillemets inverses `
... `
), mais le open()
ou le "-e
"
échoue.
Revenons donc maintenant au script finger.cgi
. Si on
prend le cas du point-virgule, l'URL
finger.cgi?login=kmaster;cat%20/etc/passwd
ne nous donne pas le
résultat escompté car le point-virgule est échappé par l'expression
régulière. Ceci signifie que le shell reçoit l'instruction :
/usr/bin/finger kmaster\;cat /etc/passwdLes erreur suivantes sont inscrites dans les logs du serveur web :
finger: kmaster;cat: no such user. finger: /etc/passwd: no such user.Ces messages sont identiques à ceux obtenus en tapant directement cette ligne dans un shell. Le problème vient de ce que la protection mise sur le '
;
' fait apparaître ce caractère comme
appartenant au nom "kmaster;cat
".
;
' : finger.cgi?login=kmaster\;cat%20/etc/passwd
.
La chaîne "\;
sera alors
transformée par le script en "\\;
", qui sera ensuite
transmise au shell. Celui-ci voit donc :
/usr/bin/finger kmaster\\;cat /etc/passwdLe shell décompose ceci en deux instructions :
/usr/bin/finger kmaster\
qui a de forte chance
d'échouer ... mais ça n'est pas notre problème ;-)
cat /etc/passwd
qui affiche le fichier de mots de
passe.
\
' doit également être échappé.
Parfois, le paramètre est "protégé" à l'aide de guillemets.
Nous avons légèrement modifié le script finger.cgi
précédent pour protéger ainsi la variable $login
.
Toutefois, si les guillemets ne
sont pas échappés, c'est comme si rien n'était fait. En effet, il
suffit simplement d'en ajouter dans sa propre requête. Ainsi, le
premier guillemet transmis ferme celui (ouvrant) du script. Ensuite,
on place la commande de notre choix, puis un second guillemet qui
ouvre le dernier guillemet (fermant) du script.
Le script finger2.cgi
(source) illustre ceci :
#finger2.cgi print "<BODY>"; $login = $input{'login'}; $login =~ s/\0//g; $login =~ s/([<>\*\|`&\$!#\(\)\[\]\{\}:'\n])/\\$1/g; print "Login $login<BR>\n"; print "Finger<BR>\n"; $CMD= "/usr/bin/finger \"$login\"|"; #Nouvelle super protection (in)efficace open(FILE,"$CMD") || goto form; while(<FILE>) { print; }
finger2.cgi?login=kmaster%22%3Bcat%20%2Fetc%2Fpasswd%3B%22
/usr/bin/finger "$login";cat
/etc/passwd""
où les guillemets ne sont plus un problème.
Lorsque vous programmez en Perl, utilisez l'option w
ou
"use warnings;
" (Perl 5.6.0 et ultérieurs), elle vous
prévient de certains problèmes potentiels, comme des variables non
initialisées ou des expressions/fonctions obsolètes.
L'option T
( taint mode de to taint, corrompre en Anglais)
offre un niveau de sécurité supérieur. Ce mode déclenche plusieurs
tests. Le plus intéressant concerne une éventuelle corruption
des variables. Celles-ci
sont soit propres, soit corrompues. Toutes les données qui proviennent
de l'extérieur du programme sont considérées comme corrompues tant
qu'elles n'ont pas été nettoyées. De même, toutes
les variables issues de variables corrompues sont, elles aussi, dans cet
état. Une telle variable ne peut alors affecter quoique ce soit en
dehors du programme.
En mode corruption, les arguments de la ligne de commande, les
variables d'environnement, le résultats de certains appels-système
(readdir()
, readlink()
,
readdir()
, ...), les données provenant de fichiers sont
considérés comme a priori suspects, et donc corrompus.
Pour nettoyer une variable, il suffit de la filtrer au travers d'une
expression régulière. Bien évidemment, utiliser .*
ne sert
à rien. Le but de cette opération est de vous forcer à prendre garde
aux arguments qui vont sont fournis.
Cependant, ce mode ne protège pas de tout : la corruption des
arguments passés sous forme de liste à system()
ou
exec()
n'est
pas vérifiée. Vous devez donc être particulièrement méticuleux si un
de vos scripts utilise ces fonctions. L'instruction
exec "sh", '-c', $arg;
est considérée comme
sécurisé, que $arg
soit corrompue ou pas :(
Il est aussi fortement
conseillé d'ajouter un "use strict;" au début de vos programmes. Cela
oblige à déclarer vos variables, certains vont trouver ça pénible,
mais c'est obligatoire si vous faites du mod-perl
.
Ainsi vos scripts CGI en Perl doivent commencer par :
#!/usr/bin/perl -wT use strict; use CGI;ou depuis Perl 5.6.0 par :
#!/usr/bin/perl -T use warnings; use strict; use CGI;
open()
De nombreux programmeurs ouvre un fichier simplement avec
open(FILE,"$filename") || ...
. Nous avons déjà vu les
risques liés à ceci. Pour les réduire, il suffit de préciser le mode
d'ouverture :
open(FILE,"<$filename") || ...
pour la lecture
seule ;
open(FILE,">$filename") || ...
pour l'écriture
seule ;
Avant d'accéder à un fichier, il est conseillé de vérifier aussi que le fichier en question existe bien. Ceci ne protège pas des problèmes de condition de concurrence (race conditions) que nous avons étudié dans le dernier article, mais permet d'éviter certains pièges comme les commandes avec des arguments.
if ( -e $filename ) { ... }
Depuis Perl 5.6, il existe une syntaxe supplémentaire pour
open()
: open(FILEHANDLE,MODE,LIST)
. Avec le
mode
'<', le fichier est ouvert en lecture ; avec le mode '>' le
fichier
est tronqué, ou créé au besoin, puis ouvert en
écriture. Cela devient intéressant pour les modes où l'on communique avec d'autres
processus. Si le mode est '|-' ou '-|', l'agument LIST est
interprété comme une commande et se trouve
respectivement après ou avant le pipe.
Avant l'apparition de Perl 5.6 et du open()
à
trois arguments, certaines personnes avaient recours à la
commande sysopen()
.
Il y a deux méthodes: soit on spécifie les caractères interdits, soit on définit explicitement les caractères autorisés à l'aide d'expressions régulières. Les programmes mis en exemple devraient vous avoir convaincus qu'il est facile d'oublier de filtrer des caractères potentiellement dangereux, c'est pourquoi la seconde méthode est conseillée.
En pratique, voici ce que cela donne : on commence par vérifier que la requête ne comporte que des caractères autorisés. Ensuite, on échappe ceux considérés comme dangereux parmi ces caractères autorisés.
#!/usr/bin/perl -wT # filtre.pl # Les variables $safe et $danger définissent respectivement l'ensemble des # caractères sans risque et celui des caractères dangereux. # Il suffit d'en ajouter/enlever pour changer le filtre. # Seules les $input comportant des caractères inclus dans l'union de # ces ensembles sont valides. use strict; my $input = shift; my $safe = '\w\d'; my $danger = '&;`\'\\|"*?~<>^(){}\$\n\r\[\]'; #Note: # Le '/', l'espace et la tabulation ne sont volontairement # dans aucun des deux ensembles if ($input =~ m/^[$safe$danger]+$/g) { $input =~ s/([$danger]+)/\\$1/g; } else { die "Bad input chars in $input\n"; } print "input = [$input]\n";
$safe
contient ceux considérés comme sans risque
(ici, uniquement des chiffres et des lettres) ;
$danger
contient les caractères à échapper car
potentiellement dangereux.
Sans vouloir déclencher une polémique, il est à mon sens souvent
préférable de choisir d'écrire ses scripts en PHP plutôt qu'en
Perl. Plus exactement, en tant qu'administrateur-système, je préfère
autoriser mes utilisateurs à utiliser le langage PHP plutôt que le
langage Perl. Une personne programmant mal - de façon non-sécurisé - en Perl
le fera aussi en PHP. Alors pourquoi préférer le PHP ?
Même si on retrouve certains problèmes de programmation en PHP,
on peut activer le Safe mode (safe_mode=on
) ou bien
désactiver certaines fonctions
(disable_functions=...
). Ce mode empêche d'accéder
aux fichiers n'appartenant pas à l'utilisateur, de modifier les
variables d'environnement sauf autorisation explicite, d'exécuter des
commandes, etc.
Par défaut, la bannière d'Apache nous renseigne sur la présence éventuelle de PHP.
$ telnet localhost 80 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. HEAD / HTTP/1.0 HTTP/1.1 200 OK Date: Tue, 03 Apr 2001 11:22:41 GMT Server: Apache/1.3.14 (Unix) (Red-Hat/Linux) mod_ssl/2.7.1 OpenSSL/0.9.5a PHP/4.0.4pl1 mod_perl/1.24 Connection: close Content-Type: text/html Connection closed by foreign host.Il suffit d'un
expose_PHP = Off
dans /etc/php.ini
pour la masquer :
Server: Apache/1.3.14 (Unix) (Red-Hat/Linux) mod_ssl/2.7.1 OpenSSL/0.9.5a mod_perl/1.24
Le fichier /etc/php.ini
(PHP4) ou /etc/httpd/php3.ini
recèle de nombreux
paramètres permettant
de durcir le système. Par exemple, l'option
"magic_quotes_gpc
" ajoute
des quotes sur les arguments reçus par les méthodes GET
,
POST
et via
les cookies, cela élimine déjà un bon nombre de problèmes présentés
dans nos exemples en Perl.
De tous les articles présentés, celui-ci est certainement le plus
facilement compréhensible. Il montre des failles exploitables tous les
jours sur le web. Il en existe d'autres, souvent liées à une mauvaise
programmation (par exemple, un script qui envoie un mail, prenant en
argument le champ From:
, offre un sympathique moyen de
spam). Les exemples sont (trop) nombreux. A partir du moment où un
script se retrouve sur un site web, il est fort à parier qu'au moins
une personne cherchera à l'exploiter de manière malveillante.
Il termine notre série sur le thème de la
programmation sécurisée. Nous espérons vous avoir fait
découvrir les principales failles de sécurité
présentes dans de trop nombreuses applications, et surtout que cela vous
incitera à bien prendre en compte le paramètre "sécurité" lors de
la conception et la programmation de vos applications.
Les problèmes de sécurité sont trop souvent négligés
en raison de la portée limitée du développement (usage interne,
installation sur un réseau privé, maquette temporaire, etc.). Et pourtant,
même un module à diffusion très restreinte peut servir un jour de base
à une application plus conséquente, pour laquelle les modifications ultérieures
se révéleront largement plus coûteuses.
Unicode | Caractère |
%00 | \0 (fin de chaîne) |
%0a | \n (retour chariot) |
%20 | espace |
%21 | ! |
%22 | " |
%23 | # |
%26 | & (ampersand) |
%2f | / |
%3b | ; |
%3c | < |
%3e | > |
man perlsec
: la page man de Perl sur la
sécurité ;
guestbook.cgi
bogué#!/usr/bin/perl -w # guestbook.cgi BEGIN { $ENV{PATH} = '/usr/bin:/bin' } delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Make %ENV safer =:-) print "Content-type: text/html\n\n"; print "<HTML>\n<HEAD><TITLE>Buggy Guestbook</TITLE></HEAD>\n"; &ReadParse(*input); my $email= $input{email}; my $texte= $input{texte}; $texte =~ s/\n/<BR>/g; print "<BODY><A HREF=\"guestbook.html\">GuestBook</A><BR><form action=\"$ENV{'SCRIPT_NAME'}\">\nEmail: <input type=texte name=email><BR>\nTexte:<BR>\n<textarea name=\"texte\" rows=15 cols=70></textarea><BR><input type=submit value=\"Go!\"></form>\n"; print "</BODY>\n"; print "</HTML>"; open (FILE,">>guestbook.html") || die ("Cannot write\n"); print FILE "Email: $email<BR>\n"; print FILE "Texte: $texte<BR>\n"; print FILE "<HR>\n"; close(FILE); exit(0); sub ReadParse { local (*in) = @_ if @_; my ($i, $key, $val); my $in_first; my @in_second; # Read in text if (&MethGet) { $in_first = $ENV{'QUERY_STRING'}; } elsif ($ENV{'REQUEST_METHOD'} eq "POST") { read(STDIN,$in_first,$ENV{'CONTENT_LENGTH'}); } @in_second = split(/&/,$in_first); foreach $i (0 .. $#in_second) { # Convert plus's to spaces $in_second[$i] =~ s/\+/ /g; # Split into key and value. ($key, $val) = split(/=/,$in_second[$i],2); # splits on the first =. # Convert %XX from hex numbers to alphanumeric $key =~ s/%(..)/pack("c",hex($1))/ge; $val =~ s/%(..)/pack("c",hex($1))/ge; # Associate key and value $in{$key} .= "\0" if (defined($in{$key})); # \0 is the multiple separator $in{$key} .= $val; } return length($#in_second); } sub MethGet { return ($ENV{'REQUEST_METHOD'} eq "GET"); }
Christophe BLAESS - ccb@club-internet.fr Christophe GRENIER - grenier@cgsecurity.org Frédéreric RAYNAL - pappy@users.sourceforge.net