有人可以提供一个示例,说明为什么我应该进行 HTML 编码,然后
使用 .innerHTML 时,JS 编码,而不是在 HTML 中双重编码
方法?
当然。
假设“用户提供的数据”是由服务器填充到您的 JavaScript 中的,那么您将必须进行 JS 编码才能获得它。
以下是服务器端的伪代码,但前端是 JavaScript:
var userProdividedData = "<%=serverVariableSetByUser %>";
element.innerHTML = userProdividedData;
像 ASP.NET <%= %> 输出服务器端变量而不进行编码。如果用户是“好”并提供值 foo,那么这将导致以下 JavaScript 被呈现:
var userProdividedData = "foo";
element.innerHTML = userProdividedData;
目前没有问题。
现在假设恶意用户提供了值"; alert("xss attack!");//。这将呈现为:
var userProdividedData = ""; alert("xss attack!");//";
element.innerHTML = userProdividedData;
这将导致 XSS 漏洞利用,其中代码实际上在上面的第一行中执行。
为了防止这种情况,正如你所说的你 JS 编码。 OWASP XSS prevention cheat sheet rule #3 说:
除了字母数字字符,转义所有小于
256 使用 \xHH 格式,以防止切换出数据值
进入脚本上下文或其他属性。
因此,为了防止这种情况,您的代码将是
var userProdividedData = "<%=JsEncode(serverVariableSetByUser) %>";
element.innerHTML = userProdividedData;
JsEncode 根据 OWASP 建议进行编码。
这将阻止上述攻击,因为它现在呈现如下:
var userProdividedData = "\x22\x3b\x20alert\x28\x22xss\x20attack\x21\x22\x29\x3b\x2f\x2f";
element.innerHTML = userProdividedData;
现在您已经保护了 JavaScript 变量分配免受 XSS 攻击。
但是,如果恶意用户提供<img src="xx" onerror="alert('xss attack')" /> 作为值怎么办?这对于变量赋值部分来说很好,因为它会像上面一样简单地转换为十六进制实体。
然而行
element.innerHTML = userProdividedData;
将导致alert('xss attack') 在浏览器呈现内部 HTML 时被执行。这就像DOM Based XSS 攻击,因为它使用的是呈现的 JavaScript 而不是 HTML,但是,当它通过服务器时,它仍然被归类为反射或存储的 XSS,具体取决于最初设置的位置。
这就是为什么您也需要进行 HTML 编码的原因。这可以通过如下函数来完成:
function escapeHTML (unsafe_str) {
return unsafe_str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/\"/g, '"')
.replace(/\'/g, ''')
.replace(/\//g, '/')
}
编写代码
element.innerHTML = escapeHTML(userProdividedData);
或者可以通过 JQuery 的 text() 函数来完成。
关于 cmets 问题的更新
我还有一个问题:你提到我们必须 JS 编码
因为攻击者可以输入"; alert("xss attack!");//。但是如果我们
会使用 HTML 编码而不是 JS 编码,不是吗?
HTML 编码 " 符号并使这种攻击不可能,因为我们
会有:var userProdividedData ="&quot;; alert(&quot;xss attack!&quot;);&#x2F;&#x2F;";
我将您的问题理解为:与其先进行 JS 编码,然后再进行 HTML 编码,我们为什么不首先不直接进行 HTML 编码,然后就这样?
因为他们可以对攻击进行编码,例如<img src="xx" onerror="alert('xss attack')" />,所有这些攻击都使用\xHH 格式进行编码以插入其有效负载 - 这将在不使用任何 HTML 编码会影响的字符的情况下实现所需的攻击 HTML 序列。
还有其他一些攻击:如果攻击者输入\,那么他们可以强制浏览器错过结束引号(因为\ 是JavaScript 中的转义字符)。
这将呈现为:
var userProdividedData = "\";
这将触发 JavaScript 错误,因为它不是正确终止的语句。如果将应用程序呈现在显眼的位置,这可能会导致拒绝服务。
另外说有两条用户控制的数据:
var userProdividedData = "<%=serverVariableSetByUser1 %>" + ' - ' + "<%=serverVariableSetByUser2 %>";
然后用户可以在第一个输入\,在第二个输入;alert('xss');//。这会将字符串连接变成一个大任务,然后是 XSS 攻击:
var userProdividedData = "\" + ' - ' + ";alert('xss');//";
由于这些极端情况,建议遵循 OWASP 指南,因为它们尽可能地接近防弹。您可能认为在 HTML 编码值列表中添加 \ 可以解决此问题,但是在以这种方式呈现内容时使用 JS 后跟 HTML 还有其他原因,因为此方法也适用于属性值中的数据:
<a href="javascript:void(0)" onclick="myFunction('<%=JsEncode(serverVariableSetByUser) %>'); return false">
不管是单引号还是双引号:
<a href='javascript:void(0)' onclick='myFunction("<%=JsEncode(serverVariableSetByUser) %>"); return false'>
甚至不加引号:
<a href=javascript:void(0) onclick=myFunction("<%=JsEncode(serverVariableSetByUser) %>");return false;>
如果您像评论中提到的那样对实体值进行 HTML 编码:
onclick='var userProdividedData ="&quot;;"'(缩短版)
代码实际上首先通过浏览器的 HTML 解析器运行,所以 userProdividedData 将是
";;
而不是
";
因此,当您将其添加到 innerHTML 调用时,您将再次遇到 XSS。请注意,<script> 块不会通过浏览器的 HTML 解析器进行处理,除了结束 </script> 标记 but 即 another story。
如上所示,尽可能编码为 late 总是明智的。然后,如果您需要在 JavaScript 上下文以外的任何内容中输出该值(例如,实际的警报框不会呈现 HTML,那么它仍然会正确显示)。
也就是说,有了上面我可以调用
alert(serverVariableSetByUser);
就像设置 HTML 一样简单
element.innerHTML = escapeHTML(userProdividedData);
在这两种情况下,它都会正确显示,而某些字符不会中断输出或导致不需要的代码执行。