OVH Cloud OVH Cloud

[PHP] XSS, charsets et htmlentities()

14 réponses
Avatar
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/function.htmlentities.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

4 réponses

1 2
Avatar
Olivier Miakinen
Le 02/03/2006 20:08, ftc répondait à John Gallet :

Et la spécification du charset par le serveur ?


Cf. mon dernier article.

[...] c'est un problème de navigateur.


Le problème vient essentiellement des navigateurs dont la détection de
charset est réglée sur automatique, même Firefox se fait berner.


Tests faits, Firefox ne se fait pas berner (idem que Mozilla).

Bon, donc on en conclut 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)



Oui. Je préciserais : il faut spécifier explicitement *dans les entêtes
HTTP* le charset utilisé dans les pages HTML. La différence entre les
deux n'a rien à voir avec la faille dont on discute en ce moment, mais
Patrick Mevzek se fera un plaisir d'en parler à ceux qui ne sont pas
convaincus... ;-)

et que htmlentities n'a **RIEN** à voir là dedans, ai-je tort ?



Tu as raison (modulo la « fausse impression de sécurité » que donnent
certains outils).

Le problème est que souvent justement on ne spécifie pas l'encodage.


Oh ! Qui est « on » ?

La seule solution que j'ai trouvé est un bout de code extrait de SafeHTML:
[ quelques dizaines de lignes de code ]


C'est quoi, déjà, la devise des shadoks ? Parce que quand on n'a pas
envie de donner un contournement compliqué à une question simple, il
y a quand même :
header("Content-Type: text/html; charset=UTF-8");

--
Olivier Miakinen
Troll du plus sage chez les conviviaux : le nouveau venu, avec
son clan, s'infiltre dans les groupes de nouvelles. (3 c.)


Avatar
Nicob
On Thu, 02 Mar 2006 08:52:44 +0000, John GALLET wrote:

Si je supprime l'appel à htmlentities(), la XSS a bien lieu.


Tu testes avec quel browser ? Je viens de faire quelques tests et ce qui
m'étonne, c'est que si je commente le header(), je n'ai plus de pop-up
(testé avec Opera, FireFox et IE), alors qu'IE (grâce au MIME-sniffing)
devrait tout de même l'afficher.

Bizarre ...


Nicob

Avatar
ftc
Le problème vient essentiellement des navigateurs dont la détection de
charset est réglée sur automatique, même Firefox se fait berner.


Tests faits, Firefox ne se fait pas berner (idem que Mozilla).


J'ai fait le test aussi avec Firefox 1.5 en mode détection de charset
auto et il s'est fait berner. Le mode de détection auto n'a pas l'air
très au point il me l'a détecté aléatoirement en UTF-8, UTF-16, ISO-8859-1.

Le problème est que souvent justement on ne spécifie pas l'encodage.


Oh ! Qui est « on » ?


Plein de monde ;-)

Je sais très bien que dans le meilleur des mondes il faudrait préciser
le charset ( non d'ailleurs, dans le meilleur des mondes on ne devrait
pas avoir à le mettre si on utilise les valeurs par défaut ), mais quand
on travaille dans une équipe et qu'on a pas forcément de relation
directe avec tous les intervenants, pas facile de faire passer ce genre
de message.


La seule solution que j'ai trouvé est un bout de code extrait de SafeHTML:
[ quelques dizaines de lignes de code ]


C'est quoi, déjà, la devise des shadoks ? Parce que quand on n'a pas
envie de donner un contournement compliqué à une question simple, il
y a quand même :
header("Content-Type: text/html; charset=UTF-8");


Oui, mais comme dit plus haut, on n'est pas toujours complètement maitre
de la situation, notament lorsque la sortie du PHP n'est pas envoyée
directement au navigateur mais passe par des filtres XSLT ou autre.

Et puis, il ne faut pas oublier les pages dont le charset normal est
utf-7 ;-) Je ne suis pas vraiment sûr que ça existe.


Avatar
Patrick Mevzek
Je sais très bien que dans le meilleur des mondes il faudrait préciser
le charset ( non d'ailleurs, dans le meilleur des mondes on ne devrait
pas avoir à le mettre si on utilise les valeurs par défaut ), mais quand


Bien sûr que si qu'il faut *toujours* le mettre. Il ne faut *pas* se
baser sur les valeurs par défaut. C'est la source de nombreuses failles.
Cela s'appelle la programmation sur la défensive, comme la conduite.

Et puis, il ne faut pas oublier les pages dont le charset normal est
utf-7 ;-) Je ne suis pas vraiment sûr que ça existe.


Il n'y a que IMAP qui utilise UTF-7, autrement cet encodage devrait
disparaître.

--
Patrick Mevzek . . . . . . Dot and Co (Paris, France)
<http://www.dotandco.net/> <http://www.dotandco.com/>
Dépêches sur le nommage <news://news.dotandco.net/dotandco.info.news>

1 2