2013-07-17 30 views
5

我希望能夠接受來自不受信任用戶的HTML並對其進行消毒,以便我可以安全地將其包含在網站的頁面中。我的意思是標記不應該被剝離或逃過一劫,但應該通過基本保持不變傳遞,除非它包含危險的標記,如<script>或,危險的屬性,如onload,或危險的CSS屬性,如背景的URL。 (顯然,一些較老的IE瀏覽器會在CSS中執行JavaScript網址嗎?)消毒不受信任的HTML5

提供來自不同域的內容(包含在iframe中)不是一個好的選擇,因爲沒有辦法事先告訴iframe有多高對於某些頁面來說,它總會看起來很醜陋。

我查看了HTML Purifier,但看起來它還不支持HTML5。我也研究過Google Caja,但我正在尋找一種不使用腳本的解決方案。

有沒有人知道一個圖書館會做到這一點? PHP是首選,但乞丐不能選擇器。

+0

您可以試試[raxan data sanitizer](https://searchcode.com/codesearch/view/2955473/) – Gunaseelan

+2

問我們要推薦或找到工具,圖書館或最喜愛的非現場資源的問題是堆棧溢出,因爲他們傾向於吸引自以爲是的答案和垃圾郵件。相反,請描述問題以及到目前爲止解決問題所做的工作。 –

回答

2

您可能能夠沿着線做一些事情:

preg_replace('/<\s*iframe\s+[^>]*>.*<\s*\/\s*iframe\s+[^>]*>/i', '', $html); 
preg_replace('/<\s*script\s+[^>]*>.*<\s*\/\s*script\s+[^>]*>/i', '', $html); 
preg_replace('/\s+onload\s+=\s+"[^"]+"/i', '', $html); 

...但話又說回來:你的正則表達式,現在你有兩個問題 - 這可能會刪除超過就想離開不止也想要。

但由於HTML淨化器可能是最現代的,非常適合(和開源)項目,你仍然應該使用一個,也許做出調整,如果你真的需要它們。

您可以檢查出以下以及之一:

  • kses - 事實上的標準,找到了一種方法到WordPress以及
  • htmLawed - 的進一步發展KSES
  • PHP Input Filter - 可以過濾標籤和屬性

雖然你還必須確保你自己的頁面佈局不會在包含res由於未關閉標籤而導致超標。

2

也許最好採用不同的方法? 如何告訴他們他們可以使用什麼?

在這種情況下,你可以使用使用strip_tags。這樣會更容易,更可控。很容易在未來延長

+1

>此功能不會修改您允許使用allowable_tags的標籤上的任何屬性,包括惡作劇用戶在發佈文本時可能會濫用的樣式和onmouseover屬性,這些屬性將顯示給其他用戶。 – Brian

6

黑名單方法讓你承受升級壓力。因此,每當瀏覽器開始支持新標準時,您必須將您的清理工具提升到同一水平。這種變化比你想象的更頻繁。

白名單的原因(這是用strip_tags具有明確的例外實現)縮小選項爲您的用戶,但讓你保存網站上。

在我自己的網站我要申請上非常受信任的用戶頁面(如管理員)的黑名單政策和所有其他頁面的白名單。這讓我陷入了對黑名單沒有太多努力的立場。憑藉更爲成熟的角色&權限概念,您甚至可以細化您的黑名單和白名單。


更新: 我猜你看看這個:

我是用strip_tags上標籤水平的白名單,但不接受屬性都點水平。有趣的是,HTMLpurifier似乎在屬性級別上做了白名單。謝謝,這裏是一個很好的學習。

+2

'strip_tags'不能防止危險屬性。只要標籤被允許,它根本不會觸及屬性。 – Brian

2

在Ruby上,我使用Nokogiriphp version)解析HTML內容。您可以解析用戶的數據並刪除不必要的標籤或屬性,然後將其轉換爲文本。

phpQuery - 另一個解析器。

而在PHP中有一個strip_tags函數。

或者你可以manualy刪除所有的屬性:

$dom = new DOMDocument; 
$dom -> loadHTML($html); 
$xpath = new DOMXPath($dom); 
$nodes = $xpath -> query("//*[@style]"); // all elements with style attribute 
foreach ($nodes as $node) { 
    // remove or do what you want 
    $node -> removeAttribute("style"); 
} 
echo $dom -> saveHTML(); 
+0

'DOMDocument'是否適用於HTML5? – Brian

+0

@Brian他的工作,但不好。更好地使用https://github.com/html5lib/html5lib-php – ostapische

1

WdHTMLParser類。我將這堂課用於我的論壇。

樣品與WdHTMLParser:

此類解析HTML到一個數組:

<div> 
    <span> 
     <br /> 
     <span> 
     un bout de texte 
     </span> 
     <input type="text" /> 
    </span> 
</div> 

陣列:

Array (
[0] => Array (
    [name] => div 
    [args] => Array() 
    [children] => Array (
    [0] => Array (
    [name] => span 
    [args] => Array() 
    [children] => Array (
    [0] => Array (
     [name] => br 
     [args] => Array() 
    ) 
    [1] => Array (
     [name] => span 
     [args] => Array() 
     [children] => Array (
     [0] => un bout de texte 
    ) 
    ) 
    [2] => Array (
     [name] => input 
     [args] => Array (
     [type] => text 
    ) 
    ) 
    ) 
    ) 
) 
) 
) 

WdHTMLParser陣列爲HTML

我上使用這個類我的網站將數組轉換爲HTML。

  • voyageWdHTML_allowattr:這些屬性將被允許。

  • voyageWdHTML_allowtag:這些標籤將被允許。

  • voyageWdHTML_special:制定您自己的規則。其實,我爲每個鏈接添加「_blank」。並將<br>替換爲新行(\ n)的預標籤。

  • fix_javascript:您可以啓用/禁用此功能,但它是無用的。

示例PHP:

<?php 
include "WdHTMLParser.php"; 
include "parser.php"; 

list($erreur, $message) = (new Parser())->parseBadHTML("<div> 
    <span> 
     <a onclick=\"alert('Hacked ! :'(');\">Check javascript</a> 
     <script>alert(\"lol\");</script> 
    </span> 
</div>"); 

if ($erreur) { 
    die("Error : ".$message); 
} 

echo $message; 

輸出:

<div> 
    <span> 
     <a target="_blank">Check javascript</a> 
     <pre>alert("lol");</pre> 
    </span> 
</div> 

我的分析器類:

<?php 
class Parser { 
    //private function fix_javascript(&$message) { } 

    private function voyageWdHTML_args($tab_args, $objname) { 
     $html = ""; 
     foreach ($tab_args as $attr => $valeur) { 
      if ($valeur !== null && $this->voyageWdHTML_allowattr($attr)) { 
       $html .= " $attr=\"".htmlentities($valeur)."\""; 
      } 
     } 
     return $html; 
    } 

    private function voyageWdHTML_allowattr($attr) { 
     return in_array($attr, array("align", "face", "size", "href", "title", "target", "src", "color", "style", 
            "data-class", "data-format")); 
    } 

    private function voyageWdHTML_allowtag($name) { 
     return in_array($name, array("br", "b", "i", "u", "strike", "sub", "sup", "div", "ol", "ul", "li", "font", "span", "code", 
            "hr", "blockquote", "cite", "a", "img", "p", "pre", "h6", "h5", "h4", "h3", "h2", "h1")); 
    } 

    private function voyageWdHTML_special(&$obj) { 
     if ($obj["name"] == "a") { $obj["args"]["target"] = "_blank"; } 
     if ($obj["name"] == "pre") { 
      array_filter($obj["children"], function (&$var) { 
       if (is_string($var)) { return true; } 
       if ($var["name"] == "br") { $var = "\n"; return true; } 
       return false; 
      }); 
     } 
    } 

    private function voyageWdHTML($tableau, $lvl = 0) { 
     $html = ""; 
     foreach ($tableau as $obj) { 
      if (is_array($obj)) { 
       if (!$this->voyageWdHTML_allowtag($obj["name"])) { 
        $obj["name"] = "pre"; 
        if (!isset($obj["children"])) { 
         $obj["children"] = array(); 
        } 
       } 
       if (isset($obj["children"])) { 
        $this->voyageWdHTML_special($obj); 
        $html .= "<{$obj["name"]}{$this->voyageWdHTML_args($obj["args"], $obj["name"])}>{$this->voyageWdHTML($obj["children"], $lvl+1)}</{$obj["name"]}>"; 
       } else { 
        $html .= "<{$obj["name"]}>"; 
       } 
      } else { 
       $html .= $obj; 
      } 
     } 
     return $html; 
    } 

    public function parseBadHTML($message) { 
     $WdHTMLParser = new WdHTMLParser(); 
     $message = str_replace(array("<br>", "<hr>"), array("<br/>", "<hr/>"), $message); 
     $tableau = $WdHTMLParser->parse($message); 

     if ($WdHTMLParser->malformed) { 
      $retour = $WdHTMLParser->error; 
     } else { 
      $retour = $this->voyageWdHTML($tableau); 

      //$this->fix_javascript($retour);// To make sur 
     } 

     return array($WdHTMLParser->malformed, $retour); 
    } 
} 

WdHTMLParser類

<?php 
class WdHTMLParser { 
    private $encoding; 
    private $matches; 
    private $escaped; 
    private $opened = array(); 
    public $malformed; 
    public function parse($html, $namespace = NULL, $encoding = 'utf-8') { 
     $this->malformed = false; 
     $this->encoding = $encoding; 
     $html   = $this->escapeSpecials($html); 
     $this->matches = preg_split('#<(/?)' . $namespace . '([^>]*)>#', $html, -1, PREG_SPLIT_DELIM_CAPTURE); 
     $tree   = $this->buildTree(); 
     if ($this->escaped) { 
      $tree = $this->unescapeSpecials($tree); 
     } 
     return $tree; 
    } 
    private function escapeSpecials($html) { 
     $html = preg_replace_callback('#<\!--.+-->#sU', array($this, 'escapeSpecials_callback'), $html); 
     $html = preg_replace_callback('#<\?.+\?>#sU', array($this, 'escapeSpecials_callback'), $html); 
     return $html; 
    } 
    private function escapeSpecials_callback($m) { 
     $this->escaped = true; 
     $text   = $m[0]; 
     $text   = str_replace(array('<', '>'), array("\x01", "\x02"), $text); 
     return $text; 
    } 
    private function unescapeSpecials($tree) { 
     return is_array($tree) ? array_map(array($this, 'unescapeSpecials'), $tree) : str_replace(array("\x01", "\x02"), array('<', '>'), $tree); 
    } 
    private function buildTree() { 
     $nodes = array(); 
     $i  = 0; 
     $text = NULL; 
     while (($value = array_shift($this->matches)) !== NULL) { 
      switch ($i++ % 3) { 
       case 0: { 
        if (trim($value)) { 
         $nodes[] = $value; 
        } 
       } 
        break; 
       case 1: { 
        $closing = ($value == '/'); 
       } 
        break; 
       case 2: { 
        if (substr($value, -1, 1) == '/') { 
         $nodes[] = $this->parseMarkup(substr($value, 0, -1)); 
        } else if ($closing) { 
         $open = array_pop($this->opened); 
         if ($value != $open) { 
          $this->error($value, $open); 
         } 
         return $nodes; 
        } else { 
         $node    = $this->parseMarkup($value); 
         $this->opened[] = $node['name']; 
         $node['children'] = $this->buildTree($this->matches); 
         $nodes[]   = $node; 
        } 
       } 
      } 
     } 
     return $nodes; 
    } 
    public function parseMarkup($markup) { 
     preg_match('#^[^\s]+#', $markup, $matches); 
     $name = $matches[0]; 
     preg_match_all('#\s+([^=]+)\s*=\s*"([^"]+)"#', $markup, $matches, PREG_SET_ORDER); 
     $args = array(); 
     foreach ($matches as $m) { 
      $args[$m[1]] = html_entity_decode($m[2], ENT_QUOTES, $this->encoding); 
     } 
     return array('name' => $name, 'args' => $args); 
    } 
    public function error($markup, $expected) { 
     $this->malformed = true; 
     printf('unexpected closing markup "%s", should be "%s"', $markup, $expected); 
    } 
} 

要河畔使用,您可以使用此功能(mybb.com):

<?php 
class Parser { 
    private function fix_javascript(&$message) { 
     $js_array = array(
      "#(&\#(0*)106;?|&\#(0*)74;?|&\#x(0*)4a;?|&\#x(0*)6a;?|j)((&\#(0*)97;?|&\#(0*)65;?|a)(&\#(0*)118;?|&\#(0*)86;?|v)(&\#(0*)97;?|&\#(0*)65;?|a)(\s)?(&\#(0*)115;?|&\#(0*)83;?|s)(&\#(0*)99;?|&\#(0*)67;?|c)(&\#(0*)114;?|&\#(0*)82;?|r)(&\#(0*)105;?|&\#(0*)73;?|i)(&\#112;?|&\#(0*)80;?|p)(&\#(0*)116;?|&\#(0*)84;?|t)(&\#(0*)58;?|\:))#i", 
      "#(o)(nmouseover\s?=)#i", 
      "#(o)(nmouseout\s?=)#i", 
      "#(o)(nmousedown\s?=)#i", 
      "#(o)(nmousemove\s?=)#i", 
      "#(o)(nmouseup\s?=)#i", 
      "#(o)(nclick\s?=)#i", 
      "#(o)(ndblclick\s?=)#i", 
      "#(o)(nload\s?=)#i", 
      "#(o)(nsubmit\s?=)#i", 
      "#(o)(nblur\s?=)#i", 
      "#(o)(nchange\s?=)#i", 
      "#(o)(nfocus\s?=)#i", 
      "#(o)(nselect\s?=)#i", 
      "#(o)(nunload\s?=)#i", 
      "#(o)(nkeypress\s?=)#i" 
     ); 

     $message = preg_replace($js_array, "$1<b></b>$2$4", $message); 
    } 
} 
0

我決定只使用html5lib,蟒蛇。這是我想出了:

#!/usr/bin/env python 
import sys 
from xml.dom.minidom import Node 
import html5lib 
from html5lib import (HTMLParser, sanitizer, serializer, treebuilders, 
        treewalkers) 

parser = HTMLParser(tokenizer=sanitizer.HTMLSanitizer, 
        tree=treebuilders.getTreeBuilder("dom")) 
serializer = serializer.htmlserializer.HTMLSerializer(omit_optional_tags=False) 

document = parser.parse(sys.stdin.read(), encoding="utf-8") 
# find the <html> node 
for child in document.childNodes: 
    if child.nodeType == Node.ELEMENT_NODE and child.nodeName == 'html': 
     htmlNode = child 
# find the <body> node 
for child in htmlNode.childNodes: 
    if child.nodeType == Node.ELEMENT_NODE and child.nodeName == 'body': 
     bodyNode = child 
# serialize all children of the <body> node 
for child in bodyNode.childNodes: 
    stream = treewalkers.getTreeWalker("dom")(child) 
    sys.stdout.write(serializer.render(stream, encoding="utf-8")) 

例輸入:

<script>alert("hax")</script> 
<p onload="alert('this is a dangerous attribute')"><b>hello,</b> world</p> 

輸出示例:

&lt;script&gt;alert("hax")&lt;/script&gt; 
<p><b>hello,</b> world</p> 
+0

編輯:這隻適用於Python 2.我有一個版本,也在Python 3中工作,但我不會發布它,因爲它有點hackish 。 – Brian

0

我個人使用HTML過濾此確切目的:

http://htmlpurifier.org/docs

它運作良好,並允許您自定義每個標籤和屬性。到目前爲止,我對這個插件沒有任何安全問題。

+0

HTML淨化器尚不支持HTML5。 – Brian

+0

但它允許你定義你自己的標籤和屬性:) – morissette

+0

這是一個例子:https://gist.github.com/lluchs/3303693 – morissette