在客户端清理/重写HTML
我需要显示通过跨域请求加载的外部资源,并确保只显示“ 安全 ”的内容。
可以使用Prototype的String#stripScripts来删除脚本块。 但是像onclick
或者onerror
这样的处理程序依然存在。
至less有什么图书馆?
- 剥离脚本块,
- 杀死DOM处理程序,
- 删除黑名单标签(例如:
embed
或object
)。
那么是否有任何JavaScript相关的链接和例子呢?
更新2016年:现在有一个基于Caja消毒剂的Google Closure包。
它有一个更清洁的API,被重新编写以考虑到现代浏览器上可用的API,并与Closure编译器更好地交互。
无耻的插件:查看caja /插件/ html-sanitizer.js客户端的HTML卫生洗手,已彻底审查。
它是白名单,而不是黑名单,但白名单可根据CajaWhitelistsconfiguration
如果您想删除所有标签,请执行以下操作:
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 + '|/?[az]' + tagBody + ')>', 'gi'); function removeTags(html) { var oldHtml; do { oldHtml = html; html = html.replace(tagOrComment, ''); } while (html !== oldHtml); return html.replace(/</g, '<'); }
人们会告诉你,你可以创build一个元素,并分配innerHTML
,然后获取innerText
或textContent
,然后在那里转义实体。 不要那样做。 由于<img src=bogus onerror=alert(1337)>
会运行onerror
处理程序,即使节点从未连接到DOM,它也容易受到XSS注入的攻击。
通过将Google Caja HTML清理程序embedded到Web工作人员中,可以使其成为“networking就绪”。 杀菌剂引入的任何全局variables将被包含在工作者中,加工处理在其自己的线程中进行。
对于不支持Web Workers的浏览器,我们可以使用iframe作为单独的环境供消毒工作者使用.Timothy Chien有一个polyfill来完成这个工作,使用iframe来模拟Web Workers,所以部分工作就完成了。
Caja项目有一个关于如何使用Caja作为独立客户端清洁剂的wiki页面:
- 签出源代码,然后通过运行
ant
构build - 在您的页面中包含
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
永远不要相信客户。 如果您正在编写服务器应用程序,则假定客户端将始终提交不卫生的恶意数据。 这是一个经验法则,可以让你摆脱困境。 如果可以的话,我会build议你在服务器代码中做所有的validation和卫生工作,你知道(在合理的程度上)不会被弄乱。 也许你可以使用服务器端Web应用程序作为你的客户端代码的代理,从第三方获取并在发送给客户端之前做卫生设施?
对不起,我误解了这个问题。 不过,我支持我的build议。 如果您在发送给服务器之前在服务器上进行清理,您的用户可能会更安全。
你不能预料到每一个可能出现的怪异types的格式错误的标记,一些浏览器可能会绊倒黑名单,所以不要黑名单。 除了脚本/embedded/对象和处理程序,您可能需要删除更多的结构。
而是尝试将HTMLparsing为层次结构中的元素和属性,然后针对尽可能最小的白名单运行所有元素和属性名称。 同时检查您通过白名单的任何URL属性(请记住有更多的危险协议比JavaScript :)。
如果input是格式良好的XHTML,则上面的第一部分要容易得多。
和HTML一样,如果你能find其他的方法来避免这样做,那就去做吧。 有许多潜在的漏洞。 如果在这么多年之后,主要的networking邮件服务仍然在发现漏洞,那么你认为自己可以做得更好吗?
所以,现在是2016年,我想现在我们中的很多人都在使用npm
模块。 sanitize-html
似乎是npm的主要选项,尽pipe还有其他的 。
对这个问题的其他答案提供了如何推出自己的伟大的投入,但这是一个棘手的问题,经过充分testing的社区解决scheme可能是最好的答案。
在命令行上运行以下命令来安装: npm install --save sanitize-html
ES5: var sanitizeHtml = require('sanitize-html'); // ... var sanitized = sanitizeHtml(htmlInput);
var sanitizeHtml = require('sanitize-html'); // ... var sanitized = sanitizeHtml(htmlInput);
ES6: import sanitizeHtml from 'sanitize-html'; // ... let sanitized = sanitizeHtml(htmlInput);
import sanitizeHtml from 'sanitize-html'; // ... let sanitized = sanitizeHtml(htmlInput);
既然所有主stream浏览器都支持沙箱化的内联框架,那么我认为可以采取更为简单的方式。 如果这个答案可以被更熟悉这种安全问题的人审阅,我很乐意。
注意:这种方法绝对不能在IE 9及更早版本中使用。 请参阅此表以了解支持沙盒的浏览器版本。
这个想法是创build一个JavaScript禁用的隐藏的iframe,粘贴你的不可信的HTML,并让它parsing它。 然后,您可以走DOM树并复制被认为是安全的标签和属性。
这里显示的白名单只是一个例子。 什么是最好的白名单将取决于应用程序。 如果您需要一个更复杂的策略,而不仅仅是标记和属性的白名单,那么可以通过这种方法来适应这个策略,尽pipe不是这个示例代码。
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; };
你可以在这里试试。
请注意,在此示例中,我不允许使用样式属性和标签。 如果你允许他们,你可能会想parsingCSS,并确保它为你的目的是安全的。
我已经在几个现代浏览器(Chrome 40,Firefox 36 Beta,IE 11,Chrome for Android)上testing了这个function,并且在一个老版本(IE 8)上testing了它,以确保在执行任何脚本之前保留它。 我有兴趣知道是否有任何浏览器有问题,或任何我忽略的边缘情况。
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,""); }
– 黑名单 – >完整删除标签和内容
– 白名单 – >保留标签
其他标签被移除,但是标签内容被保留
白名单标签的所有属性(剩余的)被删除
上面提到的Google Caja库太复杂了,无法configuration,并且包含在我的Web应用程序项目中(所以在浏览器上运行)。 我所采取的,因为我们已经使用CKEditor组件,是使用它的内置HTML清理和白名单function,这是更容易configuration。 所以,你可以加载一个隐藏的iframe中的CKEditor实例,并执行如下操作:
CKEDITOR.instances['myCKEInstance'].dataProcessor.toHtml(myHTMLstring)
现在,如果你没有在你的项目中使用CKEditor,这可能会有些过火,因为组件本身大约有半兆字节(最小化),但是如果你有源代码,也许你可以隔离代码白名单( CKEDITOR.htmlParser
?)并使其更短。
我build议在你的生活中切割框架,从长远来看,这对你来说过于容易。
cloneNode:克隆节点将复制其所有属性及其值,但不复制事件侦听器 。
https://developer.mozilla.org/en/DOM/Node.cloneNode
虽然我已经使用treewalkers一段时间了,但下面的内容没有经过testing,它们是JavaScript中价值最低的部分之一。 以下是您可以抓取的节点types列表,通常我使用SHOW_ELEMENT或SHOW_TEXT 。
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.'); } }