XSS 预防和 .innerHTML

XSS prevention and .innerHTML

当我允许用户将数据作为参数插入 JS innerHTML 函数时,如下所示:

element.innerHTML = “User provided variable”;

我明白为了防止XSS,我必须HTML编码,然后JS编码用户输入,因为用户可以插入这样的东西:

<img src=a onerror='alert();'>

只有 HTML 或只有 JS 编码没有帮助,因为据我所知,.innerHTML 方法在将输入插入页面之前对其进行解码。使用 HTML+JS 编码,我注意到 .innerHTML 只解码 JS,但 HTML 编码仍然存在。

但我能够通过双重编码为​​ HTML 来实现相同的目的。

我的问题是:有人可以举例说明为什么我应该 HTML 编码然后 JS 编码,而不是在使用 .innerHTML 方法时在 HTML 中双重编码吗?

确保 element 的内容被正确编码(并且不会被解析为 HTML)的一个简单方法是使用 textContent 而不是 innerHTML:

element.textContent = "User provided variable with <img src=a>";

另一种选择是仅在对您打算使用的值进行编码(如果有机会,最好在服务器上)之后才使用 innerHTML

Could somebody provide an example of why I should HTML encode and then JS encode, and not double encode in HTML when using the .innerHTML method?

当然可以。

假设“用户提供的数据”由服务器填充到您的 JavaScript 中,那么您将必须进行 JS 编码才能将其获取到那里。

以下是服务器端的伪代码,但在前端JavaScript:

var userProdividedData = "<%=serverVariableSetByUser %>";
element.innerHTML = userProdividedData;

Like 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 表示:

Except for alphanumeric characters, escape all characters less than 256 with the \xHH format to prevent switching out of the data value into the script context or into another attribute.

因此,为了防止这种情况发生,您的代码应该是

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;

现在您已针对 XSS 保护您的 JavaScript 变量赋值。

但是,如果恶意用户提供 <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, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/\"/g, '&quot;')
      .replace(/\'/g, '&#39;')
      .replace(/\//g, '&#x2F;')
}

编写代码

element.innerHTML = escapeHTML(userProdividedData);

或者可以通过 JQuery 的 text() 函数完成。

关于评论中问题的更新

I just have one more question: You mentioned that we must JS encode because an attacker could enter "; alert("xss attack!");//. But if we would use HTML encoding instead of JS encoding, wouldn't that also HTML encode the " sign and make this attack impossible because we would have: 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 会是

";;

而不是

&quot;;

所以当你将它添加到 innerHTML 调用时你会再次遇到 XSS。请注意 <script> 块不会通过浏览器的 HTML 解析器处理,除了结束 </script> 标记 that's .

编码为 late 总是明智的,如上所示。然后,如果您需要在 JavaScript 上下文以外的任何内容中输出值(例如,实际的警告框不会呈现 HTML,那么它仍会正确显示)。

也就是有了上面我就可以调用

alert(serverVariableSetByUser);

就像设置HTML

一样简单
element.innerHTML = escapeHTML(userProdividedData);

在这两种情况下,它都会正确显示,而不会出现某些字符中断输出或导致不需要的代码执行。

我在 ASP.NET Webforms 应用程序中遇到过这个问题。对此的修复相对简单。

从 NuGet 包管理器安装 HtmlSanitizationLibrary 并在您的应用程序中引用它。在后面的代码中,请按以下方式使用消毒剂class。

例如,如果当前代码看起来像这样,

YourHtmlElement.InnerHtml = "Your HTML content" ;

然后,将其替换为以下内容:

string unsafeHtml = "Your HTML content"; 
YourHtmlElement.InnerHtml = Sanitizer.GetSafeHtml(unsafeHtml);

此修复程序将消除 Veracode 漏洞并确保字符串呈现为 HTML。在代码后面对字符串进行编码会将其呈现为 'un-encoded string' 而不是 RAW HTML,因为它是在呈现开始之前编码的。