【问题标题】:Is my anti XSS method OK for allowing user HTML in PHP?我的反 XSS 方法可以在 PHP 中允许用户 HTML 吗?
【发布时间】:2009-09-05 16:50:35
【问题描述】:

我正在努力寻找一种让用户提交数据的好方法,在这种情况下,允许 HTML 并让它尽可能安全和快速。

我知道这个网站上的每个人似乎都认为http://htmlpurifier.org 是这里的答案。我部分同意。 htmlpurifier 拥有最好的开源代码,用于过滤用户提交的 HTML,但解决方案非常庞大,不利于高流量站点的性能。有一天我什至可能会使用那里的解决方案,但现在我的目标是找到一种更轻量级的方法。

我已经使用下面的 2 个功能大约 2 年半了,还没有出现任何问题,但我认为是时候从这里的专业人士那里获取一些意见,如果他们能帮助我的话。

第一个函数称为 FilterHTML($string),它在用户数据保存到 mysql 数据库之前运行。第二个函数称为 format_db_value($text, $nl2br = false),我在计划显示用户提交数据的页面上使用它。

这两个函数下面是我在http://ha.ckers.org/xss.html 上找到的一堆 XSS 代码,然后我在这两个函数上运行它们,看看我的代码有多有效,我对结果有些满意,它们确实阻止了每一个我试过的代码,但我知道它显然仍然不是 100% 安全的。

请你们看看它,并就我的代码本身甚至整个 html 过滤概念给我任何建议。

我希望有一天能使用白名单方法,但htmlpurifier 是我发现唯一值得使用的解决方案,而且正如我所提到的,它不像我想要的那样轻量级。

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);
}

在网页上显示用户提交的代码时使用以下函数

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

下面的代码来自ha.ckers.org,它们似乎都在我上面的功能上失败了

我没有尝试那个网站上的每个人,虽然还有很多,这只是其中的一部分。
原始代码在每组的第一行,运行我的函数后的代码位于其下方。

<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!#$%&()*~+-_.,:;?@[/|\]^`=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
'
)
"
>

【问题讨论】:

    标签: php html xss sanitize purify


    【解决方案1】:

    唯一确定的方法是将他们可以使用的标签和属性列入白名单,并编写严格的正则表达式来验证允许的属性值。如果您想允许诸如“样式”之类的属性,那么您将具有额外的复杂性。

    黑名单只会让某些人更难攻击,但不会让使用你还没有听说过的技术的人更难。

    我会尝试使用正则表达式将缺少的结束标签添加到用户输入的内容中,并将 &lt;br&gt; 替换为 &lt;br /&gt; 等等,然后使用 SimpleXML 对其进行解析,然后对其进行迭代并删除任何不在白名单中的标签,不在给定标签的白名单中的任何属性,以及具有确实符合该属性的精确正则表达式的值的任何属性。毕竟我会使用 asXML() 来取回文本。我会从一组最少的标签和属性开始,并根据需要添加新的标签和属性,特别注意可能包含 url 的任何内容。

    【讨论】:

    • 是的,我正在寻找一个好的白名单方法,除了 html 净化器之外,公众也可以使用,它们肯定必须存在,所以很多网站都允许 html
    • 有antisamy - owasp.org/index.php/Category:OWASP_AntiSamy_Project - 但它没有很好的PHP实现。
    【解决方案2】:

    【讨论】:

    • 感谢您提供的链接,我已经检查了其中的大部分,但大多数似乎并没有在今天的代码中完成这项工作。这是一个比较它们的链接htmlpurifier.org/comparison
    • 我应该补充一下,HTML_Filter 看起来像是我可以使用 +1 的东西
    【解决方案3】:

    恕我直言,htmlawed 是最好的——精简、快速、完整的 HTML 覆盖、最灵活……标签和属性的黑名单或白名单。安全的? Defeats所有黑客XSS代码

    【讨论】:

      【解决方案4】:

      使用 PHP 的原生 HTML 解析器怎么样?

      我很好奇,所以我写了一些测试代码(需要 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);
      

      输出,没有运行 Tidy:

      看起来还可以。还是删除太多了? :) 应该比 HTMLPurifier 快得多,理论上更安全,因为它不那么宽松,而且可能也比正则表达式更快。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-06-11
        • 2010-11-25
        • 1970-01-01
        • 2018-04-01
        • 2021-08-17
        • 1970-01-01
        • 2020-06-17
        • 2010-09-12
        相关资源
        最近更新 更多