[PHP] XSS, charsets et htmlentities()
Le
John GALLET
Bonjour,
[xpost fr.comp.securite fu2 fr.comp.lang.php, tous les deux modérés]
Au cours d'une discussion récente, certains contributeurs m'ont fait
remarqué la "faille" PHP suivante, dont j'ignorais totalement l'existence :
http://shiflett.org/archive/178
Etant de nature "Saint Thomas", j'essaie donc de reproduire dans un cas
réaliste l'attaque en question, surtout que ça permet toujours de mieux
comprendre ce qui se passe.
Or, je n'arrive absolument pas à reproduire cette "faille" dans une
autre configuration qu'un charset UTF-7, ce qui n'est en rien une faille
mais simplement lié au fait que htmlentities ne reconnait pas ce
charset, et c'est clairement spécifié dans le manuel:
http://fr2.php.net/manual/en/functi...tities.php
Voici les tests que j'ai effectués :
1) le script de réception, dont on teste la vulnérabilité. J'ai
magic_quotes_gpc activées, d'où le stripslashes parce que sinon le code
JS se fait sabrer de toutes façons.
recep.php
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-7">
<title>test</title>
</head>
<body>
<?php
echo "XSS me !<BR>".htmlentities(stripslashes($_REQUEST['var']));
?>
</body>
</html>
2) le stimulateur
<?php
dl('mbstring.so'); // je n'ai pas la lib en standard, dynamic load.
define ('ATTACK_URL','http://www.mabecane.tld/recep.php');
$string = "<script>alert('Pouet');</script>";
$string = mb_convert_encoding($string, 'UTF-7');
$data=array('var'=>$string);
// on donne l'URL du service.
$ch = curl_init(ATTACK_URL);
// login/pass si le répertoire est protégé par un .htaccess
// curl_setopt($ch,CURLOPT_USERPWD,"monlogin:monpass");
// on souhaite contrôler le retour nous même dans le cas général.
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
// on se fait le luxe d'envoyer les données par POST
curl_setopt($ch, CURLOPT_POST, 1);
// On n'envoie pas de headers particuliers
curl_setopt($ch, CURLOPT_HEADER, 0);
// on remplit ici les données à poster
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
// on appelle la page distante, exactement comme un navigateur
// et $str contient le retour généré.
$str=curl_exec($ch);
curl_close($ch);
// on pourrait tripatouiller $str si on le souhaitait
echo $str;
?>
On appelle ensuite le stimulateur ou par php -f ou par navigateur.
Tests effectués avec un netscape/mozilla quelconque sur linux.
Si je laisse UTF-7 comme charset, en effet, il y a bien affichage de la
pop-up JS. En essayant au hasard n'importe quoi d'autre comme charset
cité dans le man de htmlentities, et sans jamais spécifier de charset
comme 3eme paramètre de htmlentities( ), en mettant des charsets
différents ou identiques dans le stimulateur et le script de réception,
j'ai des zigouigouis, l'affichage html du code JS, mais jamais la
pop-up. Si je supprime l'appel à htmlentities(), la XSS a bien lieu.
Etant particulièrement nul en compréhension des charsets (ça m'a
toujours brouté) j'en conclue donc que j'ai dû passer à côté de quelque
chose, mais quoi ? Ca ne marche pas mieux en faisant un formulaire à la
place d'un appel à curl, même en forçant le accept-charset dans FORM.
JG
[xpost fr.comp.securite fu2 fr.comp.lang.php, tous les deux modérés]
Au cours d'une discussion récente, certains contributeurs m'ont fait
remarqué la "faille" PHP suivante, dont j'ignorais totalement l'existence :
http://shiflett.org/archive/178
Etant de nature "Saint Thomas", j'essaie donc de reproduire dans un cas
réaliste l'attaque en question, surtout que ça permet toujours de mieux
comprendre ce qui se passe.
Or, je n'arrive absolument pas à reproduire cette "faille" dans une
autre configuration qu'un charset UTF-7, ce qui n'est en rien une faille
mais simplement lié au fait que htmlentities ne reconnait pas ce
charset, et c'est clairement spécifié dans le manuel:
http://fr2.php.net/manual/en/functi...tities.php
Voici les tests que j'ai effectués :
1) le script de réception, dont on teste la vulnérabilité. J'ai
magic_quotes_gpc activées, d'où le stripslashes parce que sinon le code
JS se fait sabrer de toutes façons.
recep.php
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-7">
<title>test</title>
</head>
<body>
<?php
echo "XSS me !<BR>".htmlentities(stripslashes($_REQUEST['var']));
?>
</body>
</html>
2) le stimulateur
<?php
dl('mbstring.so'); // je n'ai pas la lib en standard, dynamic load.
define ('ATTACK_URL','http://www.mabecane.tld/recep.php');
$string = "<script>alert('Pouet');</script>";
$string = mb_convert_encoding($string, 'UTF-7');
$data=array('var'=>$string);
// on donne l'URL du service.
$ch = curl_init(ATTACK_URL);
// login/pass si le répertoire est protégé par un .htaccess
// curl_setopt($ch,CURLOPT_USERPWD,"monlogin:monpass");
// on souhaite contrôler le retour nous même dans le cas général.
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
// on se fait le luxe d'envoyer les données par POST
curl_setopt($ch, CURLOPT_POST, 1);
// On n'envoie pas de headers particuliers
curl_setopt($ch, CURLOPT_HEADER, 0);
// on remplit ici les données à poster
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
// on appelle la page distante, exactement comme un navigateur
// et $str contient le retour généré.
$str=curl_exec($ch);
curl_close($ch);
// on pourrait tripatouiller $str si on le souhaitait
echo $str;
?>
On appelle ensuite le stimulateur ou par php -f ou par navigateur.
Tests effectués avec un netscape/mozilla quelconque sur linux.
Si je laisse UTF-7 comme charset, en effet, il y a bien affichage de la
pop-up JS. En essayant au hasard n'importe quoi d'autre comme charset
cité dans le man de htmlentities, et sans jamais spécifier de charset
comme 3eme paramètre de htmlentities( ), en mettant des charsets
différents ou identiques dans le stimulateur et le script de réception,
j'ai des zigouigouis, l'affichage html du code JS, mais jamais la
pop-up. Si je supprime l'appel à htmlentities(), la XSS a bien lieu.
Etant particulièrement nul en compréhension des charsets (ça m'a
toujours brouté) j'en conclue donc que j'ai dû passer à côté de quelque
chose, mais quoi ? Ca ne marche pas mieux en faisant un formulaire à la
place d'un appel à curl, même en forçant le accept-charset dans FORM.
JG

Poser une question


Je répondrai plus loin.
J'ai utilisé ceci pour faire la conversion en ligne :
Résultat :
$string = "+ADw-script+AD4-alert('Pouet')+ADsAPA-/script+AD4-";
Pas étonnant que htmlentities ne voie pas les « < » et « > » !
... avec la configuration par défaut concernant les encodages, je suppose.
Normal tant que le navigateur ne « devine » pas que c'est de l'UTF-7.
Ce à côté de quoi tu es passé, c'est « Internet Explorer makes this
assumption automatically » : en l'absence de charset explicite, IE
essaye de deviner, et la présence des +Axx- lui permet de réussir assez
facilement.
Note que, même si ce n'est pas le comportement par défaut de Mozilla ni
de Firefox, on conseille souvent dans fciw.navigateurs de le configurer
pour qu'il le fasse aussi. Tu peux essayer toi-même : menu « Affichage »
(View en anglais), sous-menu « Encodage des caractères », « Détection
automatique », « Universel ».
Un dernier point : si ça ne marche toujours pas, c'est peut-être que tu
as correctement configuré ton serveur, de telle sorte qu'il envoie
lui-même un charset par défaut dans les entêtes HTTP (bien évidemment
différent de UTF-7).
Cordialement,
--
Olivier Miakinen
On pourrait remplacer par :
Tu n'as pas regardé l'exposé du problème dans son intégralité. C'est
vrai que ce n'est pas directement une faille liée à PHP, c'est plutôt
une utilisation inappropriée de htmlentities et une faille de IE.
Le problème arrive dans deux circonstances :
1) le charset n'est spéciifé ni par le serveur ni dans la page de
destination, le navigateur va donc en déduire dans certaines conditions
( avec son système de détection automatique de charset ) que c'est une
page en UTF-7.
2) Le code UTF-7 est placé dans les 4096 premiers octets de la page,
dans ce cas, il semblerait que IE interprète la page comme étant du
UTF-7 malgré les charsets indiqués ( la faille a peut être été corrigée
depuis sur IE )
A noter que ce n'est pas spécifique à PHP, la faille a été à l'origine
mise en évidence sur Google.
<meta http-equiv="Content-Type" content="text/html; charset=utf-7">
Ca ne suffit pas ? (si la réponse est "non, suffit pas", la question
suivante sera "alors à quoi ça sert ?")
header('Content-Type: text/html; charset=UTF-7');
alors ça m'affiche seulement les zigouigouis que tu as écris. Donc je ne
sais pas ce qu'il détecte soit disant automatiquement, mais pas la xss.
Test fait sous un XP pro SP2 patché au max en automatique.
Ensuite, j'ai refait les tests aujourd'hui sous IE, même combat.
JG
éviter les XSS, on me répond "non, cf le lien schmoll", ledit lien
schmoll n'explique rien et donc j'essaie de comprendre où est le
problème pour ne pas avoir l'impression d'être à l'abri des attaques
alors que la terre entière rigole dans mon dos.
là dedans.
définition explicite du charset dans le meta, j'obtiens les caractères à
la con. Je n'ai rien fait de spécial dans la configuration d'IE ou de la
machine windows en question, j'en suis bien incapable.
<html lang="en">
<head>
<title>a</title>
</head>
<body>
XSS me !<BR>+ADw-script+AD4-alert('Pouet')+ADsAPA-/script+AD4-</body>
</html>
Ca doit faire moins de 4096 .
htmlentities(), c'est un problème de navigateur.
Bon, donc on en conclue quoi concrètement sur ce qu'il faut faire en PHP
et surtout sur le rôle ou l'appel de htmlentities( ) ? Moi j'en
concluerais bien qu'il faut spécifier explicitement le charset utilisé
dans les pages HTML (c'est pas un scoop, mais autant le vérifier) et que
htmlentities n'a **RIEN** à voir là dedans, ai-je tord ?
JG