代码之家  ›  专栏  ›  技术社区  ›  aemkei

在客户端清理/重写HTML

  •  67
  • aemkei  · 技术社区  · 17 年前

    我需要显示通过跨域请求加载的外部资源,并确保仅显示“ 安全的 “内容。

    可以用原型的 String#stripScripts 删除脚本块。但处理者如 onclick 或 onerror 还在那里。

    有没有图书馆至少可以

    • 删除脚本块,
    • 杀死DOM处理程序,
    • 移除黑名单标签(例如: embed 或 object ).

    那么有没有与JavaScript相关的链接和示例呢?

    10 回复  |  直到 12 年前
        1
  •  106
  •   Mike Samuel    9 年前

    2016年更新:现在 Google Closure 基于Caja消毒剂的包装。

    它有一个更干净的API,被重写以考虑到现代浏览器上可用的API,并与闭包编译器进行更好的交互。


    无耻插头:参见 caja/plugin/html-sanitizer.js 对于一个客户端html消毒剂已经彻底审查。

    它是白名单,不是黑名单,但白名单可根据 CajaWhitelists


    如果要删除所有标记,请执行以下操作:

    var tagBody = '(?:[^"\'>]|"[^"]*"|\'[^\']*\')*';
    
    var tagOrComment = new RegExp(
        '<(?:'
        // Comment body.
        + '!--(?:(?:-*[^->])*--+|-?)'
        // Special "raw text" elements whose content should be elided.
        + '|script\\b' + tagBody + '>[\\s\\S]*?</script\\s*'
        + '|style\\b' + tagBody + '>[\\s\\S]*?</style\\s*'
        // Regular name
        + '|/?[a-z]'
        + tagBody
        + ')>',
        'gi');
    function removeTags(html) {
      var oldHtml;
      do {
        oldHtml = html;
        html = html.replace(tagOrComment, '');
      } while (html !== oldHtml);
      return html.replace(/</g, '&lt;');
    }
    

    人们会告诉你,你可以创建一个元素,并分配 innerHTML 然后得到 innerText 或 textContent ,然后在其中转义实体。别那么做。它易受XSS注入攻击,因为 <img src=bogus onerror=alert(1337)> 将运行 onerror 处理程序,即使节点从未附加到DOM。

        2
  •  38
  •   Jeffery To    10 年前

    谷歌Caja HTML sanitizer 可以通过将其嵌入 web worker

    对于不支持webworker的浏览器,我们可以使用iframe作为单独的环境,让消毒剂在其中工作。钱提摩太有一个 polyfill 这就做到了,使用iframes来模拟Web工作者,所以这一部分是为我们完成的。

    Caja项目有一个wiki页面 how to use Caja as a standalone client-side sanitizer :

    • 签出源,然后通过运行 ant
    • 包括 html-sanitizer-minified.js 或 html-css-sanitizer-minified.js 在你的页面中
    • 呼叫 html_sanitize(...)

    工作脚本只需遵循以下说明:

    importScripts('html-css-sanitizer-minified.js'); // or 'html-sanitizer-minified.js'
    
    var urlTransformer, nameIdClassTransformer;
    
    // customize if you need to filter URLs and/or ids/names/classes
    urlTransformer = nameIdClassTransformer = function(s) { return s; };
    
    // when we receive some HTML
    self.onmessage = function(event) {
        // sanitize, then send the result back
        postMessage(html_sanitize(event.data, urlTransformer, nameIdClassTransformer));
    };
    

    (要使simworker库正常工作,需要更多的代码,但这对本次讨论并不重要。)

    演示: https://dl.dropbox.com/u/291406/html-sanitize/demo.html

        3
  •  17
  •   Sean Edwards    17 年前

    永远不要相信客户。如果您正在编写服务器应用程序,请假定客户端将始终提交不卫生的恶意数据。这是一条经验法则,可以让你远离麻烦。如果可以的话,我建议您在服务器代码中进行所有的验证和卫生处理,您知道(在合理的程度上)这是不会被篡改的。也许您可以使用服务器端web应用程序作为客户端代码的代理,该代码从第三方获取并在将其发送给客户端之前进行清理?

    [编辑]对不起,我误解了这个问题。然而,我坚持我的建议。如果在发送给用户之前对服务器进行清理,则用户可能会更安全。

        4
  •  12
  •   bobince    17 年前

    你不可能预料到每一种可能的奇怪类型的错误标记,某些浏览器可能会为了逃避黑名单而被绊倒,所以不要黑名单。有 许多的 除了脚本/嵌入/对象和处理程序之外,您可能需要删除更多的结构。

    相反,尝试将HTML解析为层次结构中的元素和属性,然后对尽可能少的白名单运行所有元素和属性名称。还要对照白名单检查您通过的任何URL属性(记住,有比javascript更危险的协议:)。

    如果输入是格式良好的XHTML,那么上面的第一部分就容易多了。

    与HTML清理一样,如果您可以找到其他方法来避免这样做,那么请改为这样做。有很多很多潜在的漏洞。如果主要的webmail服务在这么多年之后仍然发现漏洞,你认为你能做得更好的是什么?

        5
  •  11
  •   aldel    7 年前

    既然所有主流浏览器都支持沙盒iframe,那么 认为 可以是安全的。如果这个答案能被更熟悉这类安全问题的人审阅,我会很高兴的。

    注意:这种方法在IE9和更早版本中肯定行不通。见 this table 支持沙箱的浏览器版本。 (注:表上似乎说在Opera Mini中不起作用,但我只是试了一下,而且成功了。)

    其思想是在禁用JavaScript的情况下创建一个隐藏的iframe,将不可信的HTML粘贴到其中,并让它解析它。然后,您可以遍历DOM树并复制出被认为是安全的标记和属性。

    这里显示的白名单只是一些例子。最好的白名单取决于应用程序。如果您需要一个比标记和属性的白名单更复杂的策略,那么这个方法可以容纳它,尽管这个示例代码不能容纳它。

    var tagWhitelist_ = {
      'A': true,
      'B': true,
      'BODY': true,
      'BR': true,
      'DIV': true,
      'EM': true,
      'HR': true,
      'I': true,
      'IMG': true,
      'P': true,
      'SPAN': true,
      'STRONG': true
    };
    
    var attributeWhitelist_ = {
      'href': true,
      'src': true
    };
    
    function sanitizeHtml(input) {
      var iframe = document.createElement('iframe');
      if (iframe['sandbox'] === undefined) {
        alert('Your browser does not support sandboxed iframes. Please upgrade to a modern browser.');
        return '';
      }
      iframe['sandbox'] = 'allow-same-origin';
      iframe.style.display = 'none';
      document.body.appendChild(iframe); // necessary so the iframe contains a document
      iframe.contentDocument.body.innerHTML = input;
    
      function makeSanitizedCopy(node) {
        if (node.nodeType == Node.TEXT_NODE) {
          var newNode = node.cloneNode(true);
        } else if (node.nodeType == Node.ELEMENT_NODE && tagWhitelist_[node.tagName]) {
          newNode = iframe.contentDocument.createElement(node.tagName);
          for (var i = 0; i < node.attributes.length; i++) {
            var attr = node.attributes[i];
            if (attributeWhitelist_[attr.name]) {
              newNode.setAttribute(attr.name, attr.value);
            }
          }
          for (i = 0; i < node.childNodes.length; i++) {
            var subCopy = makeSanitizedCopy(node.childNodes[i]);
            newNode.appendChild(subCopy, false);
          }
        } else {
          newNode = document.createDocumentFragment();
        }
        return newNode;
      };
    
      var resultElement = makeSanitizedCopy(iframe.contentDocument.body);
      document.body.removeChild(iframe);
      return resultElement.innerHTML;
    };
    

    你可以试试 here .

    请注意,在本例中,我不允许使用样式属性和标记。如果你允许的话,你可能需要解析CSS并确保它对你的目的是安全的。

    我已经在几个现代浏览器(Chrome 40,Firefox 36 Beta,IE 11,Android版Chrome)和一个旧浏览器(IE 8)上测试过了,以确保它在执行任何脚本之前都能安全运行。我想知道是否有任何浏览器有问题,或任何边缘的情况,我忽略了。

        6
  •  8
  •   ericsoco    10 年前

    所以,现在是2016年,我想我们很多人都在使用 npm 我们代码中的模块。 sanitize-html 似乎是npm的首选,尽管 others .

    这个问题的其他答案为如何实现自己的目标提供了很好的帮助,但这是一个非常棘手的问题,经过良好测试的社区解决方案可能是最好的答案。

    在命令行上运行此命令以安装: npm install --save sanitize-html

    ES5标准: var sanitizeHtml = require('sanitize-html'); // ... var sanitized = sanitizeHtml(htmlInput);

    ES6标准: import sanitizeHtml from 'sanitize-html'; // ... let sanitized = sanitizeHtml(htmlInput);

        7
  •  5
  •   neu-rah    11 年前
    String.prototype.sanitizeHTML=function (white,black) {
       if (!white) white="b|i|p|br";//allowed tags
       if (!black) black="script|object|embed";//complete remove tags
       var e=new RegExp("(<("+black+")[^>]*>.*</\\2>|(?!<[/]?("+white+")(\\s[^<]*>|[/]>|>))<[^<>]*>|(?!<[^<>\\s]+)\\s[^</>]+(?=[/>]))", "gi");
       return this.replace(e,"");
    }
    

    -黑名单->完全删除标记和内容

    -白名单->保留标记

    -删除其他标记,但保留标记内容

    -白名单标签的所有属性(其余的)都被删除

        8
  •  2
  •   AsGoodAsItGets    10 年前

    上面建议的Google Caja库太复杂,无法为Web应用程序配置和包含在我的项目中(因此,在浏览器上运行)。因为我们已经使用了CKEditor组件,所以我使用的是它内置的HTML清理和白名单功能,它更易于配置。因此,您可以在一个隐藏的iframe中加载一个CKEditor实例,并执行如下操作:

    CKEDITOR.instances['myCKEInstance'].dataProcessor.toHtml(myHTMLstring)
    

    现在,如果您在项目中没有使用CKEditor,这可能有点过头了,因为组件本身大约是半兆字节(最小化),但是如果您有源代码,也许您可以隔离执行白名单的代码( CKEDITOR.htmlParser ?) 而且要短得多。

    http://docs.ckeditor.com/#!/api

    http://docs.ckeditor.com/#!/api/CKEDITOR.htmlDataProcessor

        9
  •  2
  •   Alex from Jitbit    7 年前

    [免责声明:我是作者之一]

    我们为此编写了一个“仅限web”(即“需要浏览器”)的开源库, https://github.com/jitbit/HtmlSanitizer 那就把所有的 tags/attributes/styles 除了“白名单”之外。

    用法:

    var input = HtmlSanitizer.SanitizeHtml("<script> Alert('xss!'); </scr"+"ipt>");
    

    P.S.的工作速度比“纯JavaScript”解决方案快得多,因为它使用浏览器来解析和操作DOM。如果您对“纯JS”解决方案感兴趣,请尝试 https://github.com/punkave/sanitize-html (非附属公司)

        10
  •  0
  •   John    14 年前

    我建议把框架从你的生活中删掉,从长远来看,这会让事情变得过于简单。

    克隆节点:克隆节点会复制其所有属性及其值,但是 做 不是 复制事件侦听器 .

    https://developer.mozilla.org/en/DOM/Node.cloneNode

    虽然我已经使用TreeWalker有一段时间了,但是下面的代码还没有测试,它们是JavaScript中最被低估的部分之一。下面是可以爬网的节点类型列表,通常我使用 显示元素 或 显示文本 .

    http://www.w3.org/TR/DOM-Level-2-Traversal-Range/traversal.html#Traversal-NodeFilter

    function xhtml_cleaner(id)
    {
     var e = document.getElementById(id);
     var f = document.createDocumentFragment();
     f.appendChild(e.cloneNode(true));
    
     var walker = document.createTreeWalker(f,NodeFilter.SHOW_ELEMENT,null,false);
    
     while (walker.nextNode())
     {
      var c = walker.currentNode;
      if (c.hasAttribute('contentEditable')) {c.removeAttribute('contentEditable');}
      if (c.hasAttribute('style')) {c.removeAttribute('style');}
    
      if (c.nodeName.toLowerCase()=='script') {element_del(c);}
     }
    
     alert(new XMLSerializer().serializeToString(f));
     return f;
    }
    
    
    function element_del(element_id)
    {
     if (document.getElementById(element_id))
     {
      document.getElementById(element_id).parentNode.removeChild(document.getElementById(element_id));
     }
     else if (element_id)
     {
      element_id.parentNode.removeChild(element_id);
     }
     else
     {
      alert('Error: the object or element \'' + element_id + '\' was not found and therefore could not be deleted.');
     }
    }