1

我需要用一些 HTML 标签保存一些数据,所以我不能strip_tags用于所有文本,也不能使用htmlentities,因为文本必须由标签修改。为了保护其他用户免受 XSS 攻击,我必须从标签内部删除所有 JavaScript。

做这个的最好方式是什么?

4

4 回答 4

3

如果您需要在数据库中保存 HTML 标记,而后者又想将其打印回浏览器,则使用内置 PHP 函数没有 100% 安全的方法来实现此目的。当没有 html 标签时它很容易,当您只有文本时,您可以使用内置的 PHP 函数来清除文本。

有一些功能可以从文本中清除 XSS,但它们不是 100% 安全的,并且总有一种方法可以让 XSS 被忽视。而且您的正则表达式示例很好,但是如果我使用可以说< script>alert('xss')</script>,或者正则表达式可能会错过并且浏览器会执行的其他组合怎么办。

最好的方法是使用HTML Purifier之类的东西

另请注意,有两个级别的安全性,第一个是当事物进入您的数据库时,第二个是当它们离开您的数据库时。

希望这可以帮助!

于 2013-04-09T16:13:06.477 回答
2

我建议您使用DOMDocument(with loadHTML) 加载所述 HTML,删除您不想看到的所有类型的标签和每个属性,然后保存 HTML(使用saveXMLor saveHTML)。您可以通过递归迭代文档根的子项,并用它们的内部内容替换您不想要的标签来做到这一点。由于loadHTML加载代码的方式与浏览器类似,因此它比使用正则表达式更安全。

编辑这是我制作的“净化”功能:

<?php

function purifyNode($node, $whitelist)
{
    $children = array();
    // copy childNodes since we're going to iterate over it and modify the collection
    foreach ($node->childNodes as $child)
        $children[] = $child;

    foreach ($children as $child)
    {
        if ($child->nodeType == XML_ELEMENT_NODE)
        {
            purifyNode($child, $whitelist);
            if (!isset($whitelist[strtolower($child->nodeName)]))
            {
                while ($child->childNodes->length > 0)
                    $node->insertBefore($child->firstChild, $child);

                $node->removeChild($child);
            }
            else
            {
                $attributes = $whitelist[strtolower($child->nodeName)];
                // copy attributes since we're going to iterate over it and modify the collection
                $childAttributes = array();
                foreach ($child->attributes as $attribute)
                    $childAttributes[] = $attribute;

                foreach ($childAttributes as $attribute)
                {
                    if (!isset($attributes[$attribute->name]) || !preg_match($attributes[$attribute->name], $attribute->value))
                        $child->removeAttribute($attribute->name);
                }
            }
        }
    }
}

function purifyHTML($html, $whitelist)
{
    $doc = new DOMDocument();
    $doc->loadHTML($html);

    // make sure <html> doesn't have any attributes
    while ($doc->documentElement->hasAttributes())
        $doc->documentElement->removeAttributeNode($doc->documentElement->attributes->item(0));

    purifyNode($doc->documentElement, $whitelist);
    $html = $doc->saveHTML();
    $fragmentStart = strpos($html, '<html>') + 6; // 6 is the length of <html>
    return substr($html, $fragmentStart, -8); // 8 is the length of </html> + 1
}

?>

您将purifyHTML使用不安全的 HTML 字符串和预定义的标签和属性白名单进行调用。白名单格式为 'tag' => array('attribute' => 'regex')。白名单中不存在的标签将被剥离,其内容内联在父标签中。白名单中给定标签不存在的属性也将被删除;白名单中存在但与正则表达式不匹配的属性也将被删除。

这是一个例子:

<?php

$html = <<<HTML
<p>This is a paragraph.</p>
<p onclick="alert('xss')">This is an evil paragraph.</p>
<p><a href="javascript:evil()">Evil link</a></p>
<p><script>evil()</script></p>
<p>This is an evil image: <img src="error.png" onerror="evil()"/></p>
<p>This is nice <b>bold text</b>.</p>
<p>This is a nice image: <img src="http://example.org/image.png" alt="Nice image"></p>
HTML;

// whitelist format: tag => array(attribute => regex)
$whitelist = array(
    'b' => array(),
    'i' => array(),
    'u' => array(),
    'p' => array(),
    'img' => array('src' => '@\Ahttp://.+\Z@', 'alt' => '@.*@'),
    'a' => array('href' => '@\Ahttp://.+\Z@')
);

$purified = purifyHTML($html, $whitelist);
echo $purified;

?>

结果是:

<p>This is a paragraph.</p>
<p>This is an evil paragraph.</p>
<p><a>Evil link</a></p>
<p>evil()</p>
<p>This is an evil image: <img></p>
<p>This is nice <b>bold text</b>.</p>
<p>This is a nice image: <img src="http://example.org/image.png" alt="Nice image"></p>

显然,您不想允许任何on*属性,我建议不要style因为奇怪的专有属性,例如behavior. 确保使用与完整字符串( ) 匹配的正则表达式验证所有 URL 属性\Aregex\Z

于 2013-04-09T16:06:25.700 回答
2

如果要允许特定标签,则必须解析 HTML。

为此已经有一个不错的库:HTML Purifier(LGPL 下的开源)

于 2013-04-09T16:06:58.733 回答
0

我为此编写了此代码,您可以设置删除的标签和属性列表

function RemoveTagAttribute($Dom,$Name){
    $finder = new DomXPath($Dom);
    if(!is_array($Name))$Name=array($Name);
    foreach($Name as $Attribute){
        $Attribute=strtolower($Attribute);
        do{
          $tag=$finder->query("//*[@".$Attribute."]");
          //print_r($tag);
          foreach($tag as $T){
            if($T->hasAttribute($Attribute)){
               $T->removeAttribute($Attribute);
            }
          }
        }while($tag->length>0);  
    }
    return $Dom;

}
function RemoveTag($Dom,$Name){
    if(!is_array($Name))$Name=array($Name);
    foreach($Name as $tagName){
        $tagName=strtolower($tagName);
        do{
          $tag=$Dom->getElementsByTagName($tagName);
          //print_r($tag);
          foreach($tag as $T){
            //
            $T->parentNode->removeChild($T);
          }
        }while($tag->length>0);
    }
    return $Dom;

}

例子:

  $dom= new DOMDocument; 
   $HTML = str_replace("&", "&amp;", $HTML);  // disguise &s going IN to loadXML() 
  // $dom->substituteEntities = true;  // collapse &s going OUT to transformToXML() 
   $dom->recover = TRUE;
   @$dom->loadHTML('<?xml encoding="UTF-8">' .$HTML); 
   // dirty fix
   foreach ($dom->childNodes as $item)
    if ($item->nodeType == XML_PI_NODE)
      $dom->removeChild($item); // remove hack
   $dom->encoding = 'UTF-8'; // insert proper
  $dom=RemoveTag($dom,"script");
  $dom=RemoveTagAttribute($dom,array("onmousedown","onclick"));
  echo $dom->saveHTML();
于 2013-04-09T17:31:57.303 回答