【问题标题】:XSS prevention and .innerHTMLXSS 预防和 .innerHTML
【发布时间】:2015-08-20 02:34:03
【问题描述】:

当我允许用户像这样将数据作为参数插入 JS innerHTML 函数时:

element.innerHTML = “User provided variable”;

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

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

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

但我可以通过双重编码为​​ HTML 来实现同样的效果。

我的问题是:有人可以提供一个示例,说明为什么在使用.innerHTML 方法时我应该先进行 HTML 编码,然后再进行 JS 编码,而不是在 HTML 中进行双重编码?

【问题讨论】:

    标签: javascript encoding xss innerhtml


    【解决方案1】:

    有人可以提供一个示例,说明为什么我应该进行 HTML 编码,然后 使用 .innerHTML 时,JS 编码,而不是在 HTML 中双重编码 方法?

    当然。

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

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

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

    像 ASP.NET &lt;%= %&gt; 输出服务器端变量而不进行编码。如果用户是“好”并提供值 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 攻击。

    但是,如果恶意用户提供&lt;img src="xx" onerror="alert('xss attack')" /&gt; 作为值怎么办?这对于变量赋值部分来说很好,因为它会像上面一样简单地转换为十六进制实体。

    然而行

    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() 函数来完成。

    关于 cmets 问题的更新

    我还有一个问题:你提到我们必须 JS 编码 因为攻击者可以输入"; alert("xss attack!");//。但是如果我们 会使用 HTML 编码而不是 JS 编码,不是吗? HTML 编码 " 符号并使这种攻击不可能,因为我们 会有:var userProdividedData ="&amp;quot;; alert(&amp;quot;xss attack!&amp;quot;);&amp;#x2F;&amp;#x2F;";

    我将您的问题理解为:与其先进行 JS 编码,然后再进行 HTML 编码,我们为什么不首先不直接进行 HTML 编码,然后就这样?

    因为他们可以对攻击进行编码,例如&lt;img src="xx" onerror="alert('xss attack')" /&gt;,所有这些攻击都使用\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 ="&amp;quot;;"'(缩短版)

    代码实际上首先通过浏览器的 HTML 解析器运行,所以 userProdividedData 将是

    ";;
    

    而不是

    &quot;;
    

    因此,当您将其添加到 innerHTML 调用时,您将再次遇到 XSS。请注意,&lt;script&gt; 块不会通过浏览器的 HTML 解析器进行处理,除了结束 &lt;/script&gt; 标记 butanother story

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

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

    alert(serverVariableSetByUser);
    

    就像设置 HTML 一样简单

    element.innerHTML = escapeHTML(userProdividedData);
    

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

    【讨论】:

    • 谢谢!这有很大帮助!我还有一个问题:您提到我们必须进行 JS 编码,因为攻击者可以输入"; alert("xss attack!");//。但是,如果我们使用 HTML 编码而不是 JS 编码,那么 HTML 是否也会对 " 符号进行编码并使这种攻击变得不可能,因为我们会有:var userProdividedData ="&amp;quot;; alert(&amp;quot;xss attack!&amp;quot;);&amp;#x2F;&amp;#x2F;";
    • @pineappleman:当然 - 您的问题的答案现在已添加到我的答案中。
    • 你能给我指出一个执行 JsEncode 工作的 javascript 库吗?我不明白如何使用它。
    • 如果输入元素不是文本字段而是选择下拉列表,其值稍后仅在客户端用于更改某些 CSS 内容。那么这听起来不像是 XSS 问题,我错了吗?
    • @user3366706 如果在没有上述考虑的情况下将值输出到页面,那么它一个XSS问题。页面逻辑适用于页面的输入,源是否来自下拉列表无关紧要,因为攻击者可以手动构建传递的输入。在最基本的层面上,想象攻击者将 HTML 更改为将 &lt;select name="foo"&gt;&lt;input name="foo" /&gt; 交换。
    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

      我在我的 ASP.NET Webforms 应用程序中遇到了这个问题。解决这个问题相对简单。

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

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

      YourHtmlElement.InnerHtml = "Your HTML content" ;
      

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

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

      此修复将消除 Veracode 漏洞并确保字符串呈现为 HTML。在后面的代码中对字符串进行编码会将其呈现为“未编码的字符串”,而不是 RAW HTML,因为它在呈现开始之前已被编码。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-11-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-08
        • 1970-01-01
        相关资源
        最近更新 更多