好的,我的第一次尝试失败了。我同意 Jan Dvorak 的评论,即最好的方法可能是使用服务器端代理中的 XSS 工具来执行此操作,特别是因为您可能不得不通过某种代理,因为您正在执行跨站点请求如果您使用的是 JSONP,那么一切都已经丢失了。
但是,由于问题要求使用 jQuery 方法来执行此操作...
理想情况下,您会找到一个用 javascript 编写的 HTML 解析器,使用它来构建元素树,并删除任何与安全属性白名单不匹配的元素或属性。
由于我不知道这样的解析器,并且由于您使用的是具有解析器的浏览器,因此我们将尝试使用它。但我们必须小心,该解析器附加到 javascript 引擎和 HTTP 客户端等。
首先,正如我在第一次尝试的反馈中指出的那样,在我们做任何会创建 DOM 元素的事情之前,我们必须做一些工作,因为有些事件可以在插入 DOM 之前运行。我们至少需要确保在创建任何 DOM 对象之前不会解析任何 onX 属性。一般来说,对预加载进行一些干扰可能也是一个好主意。为此,让我们做一些简单的文本转换:
var xmlNameStartChars = "a-zA-Z_\\u00c0-\\u00d6\\u00d8-\\u00f6\\u00f8-\\u02ff\\0370-\\u037d\\u037f-\\u1fff\\u200c\\u200d\\u2070-\\u2218f\\u2c00-\\u2fef\\u3001-\\udbbf\\udc00-\\udfff\\uf900-\\ufdcf\\ufdf0-\\ufffd";
var xmlNsPfx = "[" + xmlNameStartChars + "][-.0-9\\u00b7\\u0300-\\u036f\\u203f-\\u2040" + xmlNameStartChars + "]*:";
var tagStartRE = new RegExp("<\\/?(" + xmlNsPfx + ")?", "g");
var tagStartDeZRE = new RegExp("(<\\/(" + xmlNsPfx + ")?)z", "g");
dangerousHTMLCode = dangerousHTMLCode.replace(/on/gi, "z$&"); // run interference with onX
// run interference with preloading
// But don't interfere with namespaces
dangerousHTMLCode = dangerousHTMLCode.replace(/<\/?(\w*:)?/g, "$&z");
现在我们已经尽最大努力确保构建 DOM 树的安全性。但是,请注意,这种最大努力并不能保证安全 - 很可能存在我没有考虑过的攻击,可能围绕过去、现在或将来的浏览器或插件中的错误。一个特别担心的是,我假设唯一危险到足以在这一点上进行干预的属性以“on”开头;我认为是这样,但我对它还没有 100% 的信心。
继续自担风险
正如 Jan 在我第一次尝试的评论中向我指出的那样,白名单方法可能优于黑名单方法。我将从一个非常基本的白名单元素列表开始,添加/删除以品尝;我们将在它们前面加上z
s,因为我们的文本操作也这样做了。
var wlElements = "zdiv, zspan, zem, zstrong, zp, za, zimg, ztable, zthead, ztbody, ztfoot, ztr, zth, ztd";
var nonWlSelector = ":not(" + wlElements + ")";
var dangerousDOM = $("<div/>").html(dangerousHTMLCode);
dangerousDOM.find(nonWlSelector).remove();
现在对于有趣的部分,您必须删除危险属性。这次我将其列入黑名单,部分原因是我懒得考虑我想列入白名单的所有属性......但我在src 和 href 中将 URL 方案列入白名单,不仅仅是“javascript:”可能不安全,“ vbscript:" 和 "livescript:" 在某些浏览器中至少是危险的。您可能应该将属性列入白名单,例如,我很有可能忘记或从未知道不以“on”开头的脚本属性。我还没有找到一种在不进行暴力 DOM 遍历的情况下找到“坏”属性的方法,所以让我们这样做:
var badAttrs = /^(.*:)(zon|style|background)/i;
var suspectAttrs = /^(.*:)(src|href)$/i;
var goodSchemes = /^\s*([^:]*$|ftp:|tel:|https?:)/i;
function processAttributes(element) {
var toRemove = [];
var attrs = element.attributes;
for (var i = 0; i < attrs.length; i++) {
var name = attrs[i].name, val = attrs[i].value;
if (badAttrs.test(name) || (suspectAttr.test(name) && !goodSchemes.test(val)) {
toRemove.push(attrs[i].name);
}
}
while (toRemove.length) {
element.removeAttribute(toRemove.pop());
}
}
// Start walking from the root of our DOM fragment
var root = dangerousDOM[0];
var elements = [root];
// Walk until we have no more elements, processing their attributes and adding their children
while (elements.length) {
var elem = elements.pop();
if (elem.hasAttributes()) {
processAttributes(elem);
}
// Find children of this element and queue them up
child = elem.firstChild;
while (child) {
if (child.nodeType == 1) {
// It's an element
elements.push(child);
}
child = child.nextSibling;
}
}
现在我们准备撤消我们在开始时所做的文本操作并注入片段。同样,我们为使其更安全所做的最大努力可能没有考虑到今天有效的攻击,并且仍然可能允许对未来的浏览器/插件错误进行攻击。所以,再次,继续自担风险。
var lessDangerousHtml = dangerousDOM.html();
lessDangerousHtml = lessDangerousHtml.replace(/z(on)/gi, "$1");
lessDangerousHtml = lessDangerousHtml.replace(tagStartDeZRE, "$1");
$("#container").html(lessDangerousHtml);
非常感谢 t.niese 的建设性批评。