2009-09-05 14 views
3

Je travaille à trouver un bon moyen de rendre les données soumises par les utilisateurs, dans ce cas, autorisez HTML et faites-le aussi sûr et rapide que possible.Ma méthode anti XSS est-elle OK pour autoriser l'utilisateur HTML en PHP?

Je sais que CHAQUE PERSONNE SEULE sur ce site semble penser http://htmlpurifier.org est la réponse ici. Je suis d'accord partiellement. htmlpurifier a le meilleur code open source là-bas pour le filtrage HTML soumis par les utilisateurs, mais la solution est très encombrante et n'est pas bonne pour les performances sur un site à fort trafic. Je pourrais même utiliser la solution un jour, mais pour l'instant mon objectif est de trouver une méthode plus légère.

J'ai utilisé les 2 fonctions ci-dessous pendant environ 2 ans et demi maintenant sans aucun problème mais je pense qu'il est temps de prendre l'entrée des professionnels ici s'ils peuvent m'aider.

La première fonction est appelée FilterHTML ($ string) elle est exécutée avant que les données utilisateur ne soient enregistrées dans une base de données mysql. La deuxième fonction est appelée format_db_value ($ text, $ nl2br = false) et je l'utilise sur une page où je prévois d'afficher les données soumises par l'utilisateur. Ci-dessous les 2 fonctions est un tas de codes XSS que j'ai trouvé sur http://ha.ckers.org/xss.html et je les ai ensuite couru sur ces 2 fonctions pour voir à quel point mon code est affectif, je suis un peu satisfait des résultats, ils ont bloqué tous les codes J'ai essayé mais je sais que ce n'est toujours pas sûr à 100%.

Pouvez-vous les gars s'il vous plaît regarder par-dessus et me donner des conseils pour mon code lui-même ou même sur l'ensemble du concept de filtrage html.

Je voudrais faire une approche de liste blanche un jour, mais htmlpurifier est la seule solution que j'ai trouvé la peine d'utiliser pour cela et comme je l'ai mentionné, il n'est pas léger comme je le voudrais.

function FilterHTML($string) { 
    if (get_magic_quotes_gpc()) { 
     $string = stripslashes($string); 
    } 
    $string = html_entity_decode($string, ENT_QUOTES, "ISO-8859-1"); 
    // convert decimal 
    $string = preg_replace('/&#(\d+)/me', "chr(\\1)", $string); // decimal notation 
    // convert hex 
    $string = preg_replace('/&#x([a-f0-9]+)/mei', "chr(0x\\1)", $string); // hex notation 
    //$string = html_entity_decode($string, ENT_COMPAT, "UTF-8"); 
    $string = preg_replace('#(&\#*\w+)[\x00-\x20]+;#U', "$1;", $string); 
    $string = preg_replace('#(<[^>]+[\s\r\n\"\'])(on|xmlns)[^>]*>#iU', "$1>", $string); 
    //$string = preg_replace('#(&\#x*)([0-9A-F]+);*#iu', "$1$2;", $string); //bad line 
    $string = preg_replace('#/*\*()[^>]*\*/#i', "", $string); // REMOVE /**/ 
    $string = preg_replace('#([a-z]*)[\x00-\x20]*([\`\'\"]*)[\\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iU', '...', $string); //JAVASCRIPT 
    $string = preg_replace('#([a-z]*)([\'\"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iU', '...', $string); //VBSCRIPT 
    $string = preg_replace('#([a-z]*)[\x00-\x20]*([\\\]*)[\\x00-\x20]*@([\\\]*)[\x00-\x20]*i([\\\]*)[\x00-\x20]*m([\\\]*)[\x00-\x20]*p([\\\]*)[\x00-\x20]*o([\\\]*)[\x00-\x20]*r([\\\]*)[\x00-\x20]*t#iU', '...', $string); //@IMPORT 
    $string = preg_replace('#([a-z]*)[\x00-\x20]*e[\x00-\x20]*x[\x00-\x20]*p[\x00-\x20]*r[\x00-\x20]*e[\x00-\x20]*s[\x00-\x20]*s[\x00-\x20]*i[\x00-\x20]*o[\x00-\x20]*n#iU', '...', $string); //EXPRESSION 
    $string = preg_replace('#</*\w+:\w[^>]*>#i', "", $string); 
    $string = preg_replace('#</?t(able|r|d)(\s[^>]*)?>#i', '', $string); // strip out tables 
    $string = preg_replace('/(potspace|pot space|rateuser|marquee)/i', '...', $string); // filter some words 
    //$string = str_replace('left:0px; top: 0px;','',$string); 
    do { 
     $oldstring = $string; 
     //bgsound| 
     $string = preg_replace('#</*(applet|meta|xml|blink|link|script|iframe|frame|frameset|ilayer|layer|title|base|body|xml|AllowScriptAccess|big)[^>]*>#i', "...", $string); 
    } while ($oldstring != $string); 
    return addslashes($string); 
} 

Ci-dessous la fonction est utilisée lors de l'affichage du code soumis par l'utilisateur sur une page Web

function format_db_value($text, $nl2br = false) { 
    if (is_array($text)) { 
     $tmp_array = array(); 
     foreach ($text as $key => $value) { 
      $tmp_array[$key] = format_db_value($value); 
     } 
     return $tmp_array; 
    } else { 
     $text = htmlspecialchars(stripslashes($text)); 
     if ($nl2br) { 
      return nl2br($text); 
     } else { 
      return $text; 
     } 
    } 
} 

Les codes ci-dessous sont de ha.ckers.org et ils semblent tous échouer sur mes fonctions ci-dessus

Je n'ai pas essayé tout le monde sur ce site mais il y en a beaucoup plus, ce n'est que quelques uns d'entre eux.
Le code d'origine est sur la ligne supérieure de chaque ensemble et le code après avoir parcouru mes fonctions est sur la ligne ci-dessous.

<IMG SRC="javascript:alert(\'XSS\');"><b>hello</b> hiii 
<IMG SRC=...alert('XSS');"><b>hello</b> hiii 

<IMG SRC=JaVaScRiPt:alert('XSS')> 
<IMG SRC=...alert('XSS')> 

<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))> 
<IMG SRC=...alert(String.fromCharCode(88,83,83))> 

<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;> 
<IMG SRC=...alert('XSS')> 

<IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041> 
<IMG SRC=F MLEJNALN !> 

<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29> 
<IMG SRC=...alert('XSS')> 


<IMG SRC="jav&#x0A;ascript:alert('XSS');"> 
<IMG SRC=...alert('XSS');"> 

perl -e 'print "<IMG SRC=javascript:alert("XSS")>";' > out 
perl -e 'print "<IMG SRC=java\0script:alert(\"XSS\")>";' > out 

<BODY onload!#$%&()*~+-_.,:;[email protected][/|\]^`=alert("XSS")> 
... 

<iframe src=http://ha.ckers.org/scriptlet.html < 
... 

<LAYER SRC="http://ha.ckers.org/scriptlet.html"></LAYER> 
...... 

<META HTTP-EQUIV="Link" Content="<http://ha.ckers.org/xss.css>; REL=stylesheet"> 
...; REL=stylesheet"> 

<IMG STYLE="xss:...(alert('XSS'))"> 
<IMG STYLE="xss:expr/*XSS*/ession(alert('XSS'))"> 

<XSS STYLE="xss:...(alert('XSS'))"> 
<XSS STYLE="xss:expression(alert('XSS'))"> 

<EMBED SRC="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==" type="image/svg+xml" AllowScriptAccess="always"></EMBED> 

<EMBED SRC="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==" type="image/svg+xml" AllowScriptAccess="always"></EMBED> 


<IMG 
SRC 
= 
" 
j 
a 
v 
a 
s 
c 
r 
i 
p 
t 
: 
a 
l 
e 
r 
t 
(
' 
X 
S 
S 
' 
) 
" 
> 

<IMG 
SRC 
=... 
a 
l 
e 
r 
t 
(
' 
X 
S 
S 
' 
) 
" 
> 

Répondre

2
+0

Merci pour les liens, j'ai vérifié la plupart d'entre eux et la plupart ne semblent pas faire le travail dans le code d'aujourd'hui. Voici un lien les comparant tous http://htmlpurifier.org/comparison – JasonDavis

+0

Je devrais ajouter, HTML_Filter ressemble à quelque chose que je pourrais éventuellement utiliser +1 – JasonDavis

3

Seule façon d'être sûr est de whitelist les balises et les attributs qu'ils peuvent utiliser et écrire regexps strictes pour valider les valeurs autorisées d'attributs. Si vous souhaitez autoriser des attributs tels que "style", vous avez une complexité supplémentaire. La liste noire seulement pourrait rendre l'attaque plus difficile pour certaines personnes, mais elle ne rendra pas la tâche plus difficile pour la personne qui utilise une technique dont vous n'avez pas encore entendu parler.J'essaierais d'utiliser regexp pour ajouter des balises de fermeture manquantes à ce que les utilisateurs ont entré et remplacer <br> avec <br /> et ainsi de suite, puis l'analyser en utilisant SimpleXML, puis itérer dessus et supprimer toute balise qui n'est pas dans la liste blanche, n'importe quel attribut ce n'est pas dans la liste blanche pour une balise donnée, et tout attribut dont la valeur est conforme à une expression rationnelle précise pour cet attribut. Après tout, j'utiliserais asXML() pour récupérer le texte. Je voudrais commencer avec un ensemble minimal de balises et d'attributs et en ajouter de nouveaux au besoin en faisant particulièrement attention à tout ce qui peut contenir l'URL.

+0

Vrai Je suis à la recherche d'une bonne méthode de liste blanche disponible au public à côté purificateur html, sûrement ils doivent exister, tant de sites permettent html – JasonDavis

+0

Il y a antisamy - http://www.owasp.org/index.php/ Catégorie: OWASP_AntiSamy_Project - mais il n'a pas une bonne implémentation PHP. –

0

à mon humble avis htmlawed est le meilleur - maigre, rapide, une couverture complète HTML, plus flexible ... liste noire OU blanche pour les étiquettes et les attributs. Sûr? Defeats tous les hackers Codes XSS

0

Que diriez-vous d'utiliser l'analyseur HTML natif de PHP?

J'étais curieux à ce sujet, donc je l'ai écrit un code pour le test (nécessite PHP 5.3.6+):

$badHtml = file_get_contents('badHtml.txt'); 
$html = sprintf('<div id="input">%s</div>', $badHtml); 

// tidy is no required, but may fix invalid markup 
$tidy = new \tidy(); 
$tidy->parseString($html, array(), 'utf8'); 
$tidy->cleanRepair(); 

$dom = new \DomDocument('1.0', 'UTF-8'); 
libxml_use_internal_errors(true); 
$dom->loadHtml($tidy); 
$input = $dom->getElementById('input'); 

// tag as key, attributes as values 
$allowed = array(
    'table' => array('border'), 
    'tbody' => array(), 
    'tr'  => array(), 
    'td'  => array(), 
    'th'  => array(), 
    'img' => array('src', 'alt'), 
    'p'  => array(), 
    'ul'  => array(), 
    'ol'  => array(), 
    'li'  => array(), 
    'a'  => array('href', 'title'), 
    'strong' => array(), 
    'em'  => array(), 
    'sub' => array(), 
    'sup' => array(), 
); 

$walk = function(\DomNode $node) use($allowed, &$walk){ 

    // only check tags 
    if($node->nodeType !== XML_ELEMENT_NODE) 
    return; 

    if(!isset($allowed[$node->nodeName])) 
    return $node->parentNode->removeChild($node); 

    foreach($node->attributes as $key => $attr){ 
    if(!in_array($key, $allowed[$node->nodeName], true)) 
    $node->removeAttribute($key); 

    // expect URLs here 
    if(!in_array($key, array('href', 'src'), true)) 
     continue; 

    if(!filter_var($attr->value, FILTER_VALIDATE_URL)) 
     return $node->parentNode->removeChild($node); 

    } 

    array_map($walk, iterator_to_array($node->childNodes)); 
}; 

// convert DOMNodeList to array because this way the bad stuff 
// can be removed within the loop 
array_map($walk, iterator_to_array($input->childNodes)); 

// export HTML 
$sanitized = $dom->saveHtml($input); 

La sortie, sans courir Tidy:

enter image description here

Cela semble ok. Ou a-t-il enlevé trop? :) Devrait être beaucoup plus rapide que HTMLPurifier, théoriquement plus sécurisé car il est moins permissif, et probablement plus rapide que les expressions rationnelles.