Skip to content

DOM based XSS Prevention Cheat Sheet / 基于DOM的XSS防护技巧清单

Introduction / 介绍

Commonly discussed, overlapping categories of XSS (Cross-Site Scripting) include:

常讨论的、重叠的XSS(跨站脚本)类别包括:

The XSS Prevention Cheatsheet does an excellent job of addressing Reflected and Stored XSS. This cheatsheet addresses DOM (Document Object Model) based XSS and is an extension (and assumes comprehension) of the XSS Prevention Cheatsheet.

《XSS防护技巧清单》(Cross_Site_Scripting_Prevention_Cheat_Sheet.md)出色地解决了反射和存储XSS问题。这份清单针对基于DOM的XSS,是《XSS防护技巧清单》(Cross_Site_Scripting_Prevention_Cheat_Sheet.md)的扩展(并假设理解了该清单)

Stored and reflected describe how an attack payload reaches a victim; server-side and client-side describe where untrusted data is handled unsafely. Both stored and reflected XSS can occur on the client or server, including stored and reflected DOM-based XSS.

存储和反射描述了攻击有效载荷如何到达受害者;服务器端和客户端描述了不受信任的数据在不安全的环境中如何处理。存储和反射XSS都可以在客户端或服务器上发生,包括存储和反射的基于DOM的XSS。

For server-side XSS, the server inserts untrusted data into a response without making it safe for its output context. DOM-based XSS arises from unsafe client-side processing. In either case, the injected script executes in the browser; choose controls for the actual data flow and output context.

对于服务器端XSS,服务器在未将其输出上下文安全化的情况下将不受信任的数据插入到响应中。基于DOM的XSS源于客户端的不安全处理。在两种情况下,注入的脚本都在浏览器中执行;选择实际数据流和输出上下文的控制措施。

When a browser is rendering HTML and any other associated content like CSS or JavaScript, it identifies various rendering contexts for the different kinds of input and follows different rules for each context. A rendering context is associated with the parsing of HTML tags and their attributes.

当浏览器渲染HTML以及任何相关的CSS或JavaScript内容时,它会为不同类型的输入识别各种渲染上下文,并为每个上下文遵循不同的规则。渲染上下文与HTML标签及其属性的解析相关联。

  • The HTML parser of the rendering context dictates how data is presented and laid out on the page and can be further broken down into the standard contexts of HTML, HTML attribute, URL, and CSS.
    中文:渲染上下文的 HTML 解析器决定了数据在页面上的呈现和布局方式,并且可以进一步细分为 HTML、HTML 属性、URL 和 CSS 的标准上下文。
  • The JavaScript or VBScript parser of an execution context is associated with the parsing and execution of script code. Each parser has distinct and separate semantics in the way they can possibly execute script code which make creating consistent rules for mitigating vulnerabilities in various contexts difficult. The complication is compounded by the differing meanings and treatment of encoded values within each subcontext (HTML, HTML attribute, URL, and CSS) within the execution context.
    中文:执行上下文的 JavaScript 或 VBScript 解析器与脚本代码的解析和执行相关联。每个解析器在执行脚本代码的方式上都有独特的语义,这使得在各个上下文中创建一致的规则来缓解漏洞变得困难。这种复杂性由于执行上下文中每个子上下文(HTML、HTML 属性、URL 和 CSS)对编码值的含义和处理的差异而加剧。

For the purposes of this article, we refer to the HTML, HTML attribute, URL, and CSS contexts as subcontexts because each of these contexts can be reached and set within a JavaScript execution context.

为了本文的目的,我们将 HTML、HTML 属性、URL 和 CSS 上下文称为子上下文,因为每个上下文都可以在 JavaScript 执行上下文中访问和设置。

In JavaScript code, the main context is JavaScript but with the right tags and context closing characters, an attacker can try to attack the other 4 contexts using equivalent JavaScript DOM methods.

在 JavaScript 代码中,主要上下文是 JavaScript,但使用正确的标签和上下文结束字符,攻击者可以尝试使用等效的 JavaScript DOM 方法攻击其他 4 个上下文。

The following is an example vulnerability which occurs in the JavaScript context and HTML subcontext:

以下是一个在 JavaScript 上下文和 HTML 子上下文中发生的示例漏洞:

 <script>
 var x = '<%= taintedVar %>';
 var d = document.createElement('div');
 d.innerHTML = x;
 document.body.appendChild(d);
 </script>

Let's look at the individual subcontexts of the execution context in turn.

让我们依次查看执行上下文的各个子上下文。

RULE #1 - HTML Escape then JavaScript Escape Before Inserting Untrusted Data into HTML Subcontext within the Execution Context / 规则 #1 - 在执行上下文中的 HTML 子上下文中插入不受信任的数据之前,先进行 HTML 转义然后 JavaScript 转义

There are several methods and attributes which can be used to directly render HTML content within JavaScript. These methods constitute the HTML Subcontext within the Execution Context. If these methods are provided with untrusted input, then an XSS vulnerability could result. For example:

有几种方法和属性可以直接在 JavaScript 中渲染 HTML 内容。这些方法构成了执行上下文中的 HTML 子上下文。如果这些方法被提供不受信任的输入,则可能导致 XSS 漏洞。例如:

Example Dangerous HTML Methods / 示例危险HTML方法

Attributes / 属性

 element.innerHTML = "<HTML> Tags and markup";
 element.outerHTML = "<HTML> Tags and markup";

Methods / 方法

 document.write("<HTML> Tags and markup");
 document.writeln("<HTML> Tags and markup");

Guideline / 指南

To make dynamic updates to HTML in the DOM safe, we recommend:

为了使在DOM中动态更新HTML安全,我们建议:

  1. HTML encoding, and then
    中文:HTML编码,然后
  2. JavaScript encoding all untrusted input, as shown in these examples:
    中文:对所有不受信任的输入进行JavaScript编码,如下例所示:
 var ESAPI = require('node-esapi');
 element.innerHTML = "<%=ESAPI.encoder().encodeForJavascript(ESAPI.encoder().encodeForHTML(untrustedData))%>";
 element.outerHTML = "<%=ESAPI.encoder().encodeForJavascript(ESAPI.encoder().encodeForHTML(untrustedData))%>";
 var ESAPI = require('node-esapi');
 document.write("<%=ESAPI.encoder().encodeForJavascript(ESAPI.encoder().encodeForHTML(untrustedData))%>");
 document.writeln("<%=ESAPI.encoder().encodeForJavascript(ESAPI.encoder().encodeForHTML(untrustedData))%>");

RULE #2 - JavaScript Escape Before Inserting Untrusted Data into HTML Attribute Subcontext within the Execution Context / 规则#2 - 在将不受信任的数据插入执行上下文中的HTML属性子上下文之前进行JavaScript转义

The HTML attribute subcontext within the execution context is divergent from the standard encoding rules. This is because the rule to HTML attribute encode in an HTML attribute rendering context is necessary in order to mitigate attacks which try to exit out of an HTML attributes or try to add additional attributes which could lead to XSS.

在执行上下文中,HTML属性subcontext与标准编码规则不一致。这是因为,在HTML属性渲染上下文中对HTML属性进行编码的规则是必要的,以减轻试图退出HTML属性或尝试添加可能导致XSS攻击的额外属性的攻击行为。

When you are in a DOM execution context you only need to JavaScript encode HTML attributes which do not execute code (attributes other than event handler, CSS, and URL attributes).

当您处于DOM执行上下文时,您只需要对不执行代码的HTML属性进行JavaScript编码(除了事件处理器、CSS和URL属性)。

For example, the general rule is to HTML Attribute encode untrusted data (data from the database, HTTP request, user, back-end system, etc.) placed in an HTML Attribute. This is the appropriate step to take when outputting data in a rendering context, however using HTML Attribute encoding in an execution context will break the application display of data.

例如,一般规则是对放置在HTML属性中的不受信任数据进行HTML属性编码(来自数据库、HTTP请求、用户、后端系统等数据)。在渲染上下文中输出数据时,这是适当的步骤,然而在执行上下文中使用HTML属性编码将破坏应用程序的数据显示。

SAFE but BROKEN example / 安全但已损坏的示例

 var ESAPI = require('node-esapi');
 var x = document.createElement("input");
 x.setAttribute("name", "company_name");
 // In the following line of code, companyName represents untrusted user input
 // The ESAPI.encoder().encodeForHTMLAttribute() is unnecessary and causes double-encoding
 x.setAttribute("value", '<%=ESAPI.encoder().encodeForJavascript(ESAPI.encoder().encodeForHTMLAttribute(companyName))%>');
 var form1 = document.forms[0];
 form1.appendChild(x);

The problem is that if companyName had the value "Johnson & Johnson". What would be displayed in the input text field would be "Johnson &amp; Johnson". The appropriate encoding to use in the above case would be only JavaScript encoding to disallow an attacker from closing out the single quotes and in-lining code, or escaping to HTML and opening a new script tag.

问题是,如果companyName的值为"Johnson & Johnson"。输入文本字段中显示的内容将是"Johnson &amp; Johnson"。在上面的情况下,应使用的适当编码是仅JavaScript编码,以防止攻击者关闭单引号并内联代码,或转义到HTML并打开新的script标签。

SAFE and FUNCTIONALLY CORRECT example / 安全且功能正确的示例

 var ESAPI = require('node-esapi');
 var x = document.createElement("input");
 x.setAttribute("name", "company_name");
 x.setAttribute("value", '<%=ESAPI.encoder().encodeForJavascript(companyName)%>');
 var form1 = document.forms[0];
 form1.appendChild(x);

It is important to note that when setting an HTML attribute which does not execute code, the value is set directly within the object attribute of the HTML element so there is no concerns with injecting up.

需要注意的是,当设置不执行代码的HTML属性时,值直接设置在HTML元素的object属性中,因此不存在注入问题。

RULE #3 - Be Careful when Inserting Untrusted Data into the Event Handler and JavaScript code Subcontexts within an Execution Context / 规则#3 - 在执行上下文中将不受信任的数据插入事件处理器和JavaScript代码子上下文时,请小心处理。

Putting dynamic data within JavaScript code is especially dangerous because JavaScript encoding has different semantics for JavaScript encoded data when compared to other encodings. In many cases, JavaScript encoding does not stop attacks within an execution context. For example, a JavaScript encoded string will execute even though it is JavaScript encoded.

将动态数据放入JavaScript代码中尤其危险,因为JavaScript编码与其他编码相比具有不同的语义。在许多情况下,JavaScript编码无法阻止执行上下文中的攻击。例如,即使字符串是JavaScript编码的,它也会执行。

Therefore, the primary recommendation is to avoid including untrusted data in this context. If you must, the following examples describe some approaches that do and do not work.

因此,主要建议是避免在此上下文中包含不受信任的数据。如果必须这样做,以下示例描述了一些可行和不可行的方法。

var x = document.createElement("a");
x.href="#";
// In the line of code below, the encoded data on the right (the second argument to setAttribute)
// is an example of untrusted data that was properly JavaScript encoded but still executes.
x.setAttribute("onclick", "\u0061\u006c\u0065\u0072\u0074\u0028\u0032\u0032\u0029");
var y = document.createTextNode("Click To Test");
x.appendChild(y);
document.body.appendChild(x);

The setAttribute(name_string,value_string) method is dangerous because it implicitly coerces the value_string into the DOM attribute datatype of name_string.

setAttribute(name_string,value_string)方法很危险,因为它隐式地将value_string转换为name_string的DOM属性数据类型。

In the case above, the attribute name is an JavaScript event handler, so the attribute value is implicitly converted to JavaScript code and evaluated. In the case above, JavaScript encoding does not mitigate against DOM based XSS.

在上面的例子中,属性名称是一个JavaScript事件处理器,因此属性值被隐式转换为JavaScript代码并执行。在上面的例子中,JavaScript编码无法减轻基于DOM的XSS攻击。

Other JavaScript methods that interpret strings as code, such as setTimeout, setInterval, and Function, have the same risk. HTML event-handler attributes also contain JavaScript function bodies. Escaping every character in one payload may produce a syntax error, but JavaScript encoding is not a defense for this context: escaped identifiers remain executable when the surrounding syntax is valid.

其他将字符串解释为代码的JavaScript方法,如setTimeout、setInterval和Function,具有相同的风险。HTML事件处理器属性也包含JavaScript函数体。在一个有效负载中转义每个字符可能会产生语法错误,但JavaScript编码不是这种上下文中的防御手段:转义后的标识符在周围语法有效时仍然可执行。

<!-- Executes alert(1) when clicked; encoding the identifier does not make it safe. -->
<a id="bb" href="#" onclick="\u0061\u006c\u0065\u0072\u0074(1)">Test Me</a>

Setting the JavaScript onclick property is different from setting an HTML attribute. It expects a callback; assigning a primitive string sets it to null, whether or not the string is encoded. This is type conversion, not a benefit of JavaScript encoding. Assign a trusted function, and keep untrusted values as data inside that function rather than compiling them as code.

设置JavaScript的onclick属性与设置HTML属性不同。它期望一个回调;分配一个原始字符串将其设置为null,无论字符串是否编码。这是类型转换,而不是JavaScript编码的好处。分配一个受信任的函数,并将不受信任的值作为该函数内部的数据而不是作为代码编译。

<a id="bb" href="#">Test Me</a>
// Neither string becomes a handler.
document.getElementById("bb").onclick = "alert(7)";
document.getElementById("bb").onclick = "\u0061\u006c\u0065\u0072\u0074\u0028\u0037\u0029";

// A trusted function is a handler.
document.getElementById("bb").onclick = function () {
    alert("I was called.");
};

There are other places in JavaScript where JavaScript encoding is accepted as valid executable code.

JavaScript中还有其他地方接受JavaScript编码作为有效的可执行代码。

 for(var \u0062=0; \u0062 < 10; \u0062++){
     \u0064\u006f\u0063\u0075\u006d\u0065\u006e\u0074
     .\u0077\u0072\u0069\u0074\u0065\u006c\u006e
     ("\u0048\u0065\u006c\u006c\u006f\u0020\u0057\u006f\u0072\u006c\u0064");
 }
 \u0077\u0069\u006e\u0064\u006f\u0077
 .\u0065\u0076\u0061\u006c
 \u0064\u006f\u0063\u0075\u006d\u0065\u006e\u0074
 .\u0077\u0072\u0069\u0074\u0065(111111111);

or

或者

 var s = "\u0065\u0076\u0061\u006c";
 var t = "\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0031\u0029";
 window[s](t);

Because JavaScript is based on an international standard (ECMAScript), JavaScript encoding enables the support of international characters in programming constructs and variables in addition to alternate string representations (string escapes).

因为JavaScript基于国际标准(ECMAScript),JavaScript编码除了支持替代字符串表示(字符串转义)外,还支持编程构造和变量中的国际字符。

However the opposite is the case with HTML encoding. HTML tag elements are well defined and do not support alternate representations of the same tag. So HTML encoding cannot be used to allow the developer to have alternate representations of the <a> tag for example.

然而,HTML 编码的情况正好相反。HTML 标签元素定义良好,不支持相同标签的替代表示。因此,HTML 编码不能用来允许开发者对 <a> 标签等有替代表示形式。

HTML Encoding's Disarming Nature / HTML 编码的无害本质

In general, HTML encoding serves to castrate HTML tags which are placed in HTML and HTML attribute contexts. Working example (no HTML encoding):

一般来说,HTML 编码的作用是阉割 HTML 标签,这些标签被放置在 HTML 和 HTML 属性上下文中。工作示例(无 HTML 编码):

<a href="..." >

Normally encoded example (Does Not Work – DNW):

通常编码示例(不工作 - DNW):

&#x3c;a href=... &#x3e;

HTML encoded example to highlight a fundamental difference with JavaScript encoded values (DNW):

HTML 编码示例,以突出与 JavaScript 编码值的基本差异(DNW):

<&#x61; href=...>

If HTML encoding followed the same semantics as JavaScript encoding, the line above could have possibly worked to render a link. This difference makes JavaScript encoding a less viable weapon in our fight against XSS.

如果 HTML 编码遵循与 JavaScript 编码相同的语义,上面的行可能已经可以工作以渲染一个链接。这种差异使得 JavaScript 编码在我们对抗 XSS 的战斗中变得不那么有效。

RULE #4 - JavaScript Escape Before Inserting Untrusted Data into the CSS Attribute Subcontext within the Execution Context / 规则 #4 - 在将不受信任的数据插入执行上下文中的 CSS 属性子上下文之前进行 JavaScript 转义

Normally executing JavaScript from a CSS context required either passing javascript:attackCode() to the CSS url() method or invoking the CSS expression() method passing JavaScript code to be directly executed.

通常,从 CSS 上下文执行 JavaScript 需要传递 javascript:attackCode() 到 CSS 的 url() 方法或调用 CSS 的 expression() 方法,并将 JavaScript 代码传递以直接执行。

From my experience, calling the expression() function from an execution context (JavaScript) has been disabled. In order to mitigate against the CSS url() method, ensure that you are URL encoding the data passed to the CSS url() method.

根据我的经验,从执行上下文(JavaScript)调用 expression() 函数已被禁用。为了减轻对 CSS url() 方法的风险,确保您对传递给 CSS url() 方法的数据进行 URL 编码。

var ESAPI = require('node-esapi');
document.body.style.backgroundImage = "url(<%=ESAPI.encoder().encodeForJavascript(ESAPI.encoder().encodeForURL(companyName))%>)";

RULE #5 - URL Escape then JavaScript Escape Before Inserting Untrusted Data into URL Attribute Subcontext within the Execution Context / 规则 #5 - 在执行上下文中将不受信任的数据插入 URL 属性子上下文之前,先进行 URL 转义,然后进行 JavaScript 转义

The logic which parses URLs in both execution and rendering contexts looks to be the same. Therefore there is little change in the encoding rules for URL attributes in an execution (DOM) context.

解析执行和渲染上下文中 URL 的逻辑看起来是相同的。因此,在执行(DOM)上下文中,URL 属性的编码规则变化不大。

var ESAPI = require('node-esapi');
var x = document.createElement("a");
x.setAttribute("href", '<%=ESAPI.encoder().encodeForJavascript(ESAPI.encoder().encodeForURL(userRelativePath))%>');
var y = document.createTextElement("Click Me To Test");
x.appendChild(y);
document.body.appendChild(x);

If you utilize fully qualified URLs then this will break the links as the colon in the protocol identifier (http: or javascript:) will be URL encoded preventing the http and javascript protocols from being invoked.

如果您使用完全限定的 URL,这将破坏链接,因为协议标识符(http: 或 javascript:)中的冒号将被 URL 编码,从而阻止 http 和 javascript 协议的调用。

RULE #6 - Populate the DOM using safe JavaScript functions or properties / 规则 #6 - 使用安全的 JavaScript 函数或属性填充 DOM

The most fundamental safe way to populate the DOM with untrusted data is to use the safe assignment property textContent.

将不受信任的数据安全地填充到 DOM 的最基本方法是使用安全的赋值属性 textContent。

Here is an example of safe usage.

以下是一个安全使用的示例。

<script>
element.textContent = untrustedData;  //does not execute code
</script>

RULE #7 - Fixing DOM Cross-site Scripting Vulnerabilities / 规则 #7 - 修复 DOM 跨站脚本漏洞

The best way to fix DOM based cross-site scripting is to use the right output method (sink). For example if you want to use user input to write in a div tag element don't use innerHtml, instead use innerText or textContent. This will solve the problem, and it is the right way to re-mediate DOM based XSS vulnerabilities.

修复基于DOM的跨站脚本的最佳方法是使用正确的输出方法(汇入点)。例如,如果您想使用用户输入在div标签元素中写入内容,不要使用innerHTML,而是使用innerText或textContent。这将解决问题,并且是修复基于DOM的XSS漏洞的正确方法。

It is always a bad idea to use a user-controlled input in dangerous sources such as eval. 99% of the time it is an indication of bad or lazy programming practice, so simply don't do it instead of trying to sanitize the input.

在eval等危险源中使用用户控制的输入总是一个糟糕的想法。99%的情况下,这是不良或懒惰的编程实践的迹象,因此简单地不要这样做,而不是尝试清理输入。

Finally, to fix the problem in our initial code, instead of trying to encode the output correctly which is a hassle and can easily go wrong we would simply use element.textContent to write it in a content like this:

最后,为了修复我们初始代码中的问题,我们不会尝试正确地编码输出,这很麻烦且容易出错,而是简单地使用element.textContent以如下内容写入它:

<b>Current URL:</b> <span id="contentholder"></span>
...
<script>
document.getElementById("contentholder").textContent = document.baseURI;
</script>

It does the same thing but this time it is not vulnerable to DOM based cross-site scripting vulnerabilities.

它做的是同样的事情,但这次它不会受到基于DOM的跨站脚本漏洞的威胁。

Guidelines for Developing Secure Applications Utilizing JavaScript / 利用JavaScript开发安全应用程序的指南

DOM based XSS is extremely difficult to mitigate against because of its large attack surface and lack of standardization across browsers.

由于攻击面大且浏览器之间缺乏标准化,基于DOM的XSS非常难以缓解。

The guidelines below are an attempt to provide guidelines for developers when developing Web based JavaScript applications (Web 2.0) such that they can avoid XSS.

以下指南旨在为开发者在开发基于Web的JavaScript应用程序(Web 2.0)时提供指导,以便他们可以避免XSS。

GUIDELINE #1 - Untrusted data should only be treated as displayable text / 指南 #1 - 应仅将不受信任的数据视为可显示的文本

Avoid treating untrusted data as code or markup within JavaScript code.

避免在JavaScript代码中将不受信任的数据视为代码或标记。

GUIDELINE #2 - Always JavaScript encode and delimit untrusted data as quoted strings when entering the application when building templated JavaScript / 指南#2 - 始终在构建模板化JavaScript时,将不受信任的数据作为引号字符串进行JavaScript编码和分隔,以便将其输入应用程序。

Always JavaScript encode and delimit untrusted data as quoted strings when entering the application as illustrated in the following example.

如下示例所示,始终在输入应用程序时,将不受信任的数据作为引号字符串进行JavaScript编码和分隔。

var x = "<%= Encode.forJavaScript(untrustedData) %>";

GUIDELINE #3 - Use document.createElement("..."), element.setAttribute("...","value"), element.appendChild(...) and similar to build dynamic interfaces / 指南#3 - 使用document.createElement("...")、element.setAttribute("...","value")、element.appendChild(...)和类似方法构建动态界面

document.createElement("..."), element.setAttribute("...","value"), element.appendChild(...) and similar are safe ways to build dynamic interfaces.

使用document.createElement("...")、element.setAttribute("...","value")、element.appendChild(...)和类似方法是构建动态界面的安全方式。

Please note, element.setAttribute is only safe for a limited number of attributes.

请注意,element.setAttribute仅对有限数量的属性是安全的。

Dangerous attributes include any attribute that is a command execution context, such as onclick or onblur.

危险的属性包括任何是命令执行上下文的属性,例如onclick或onblur。

Examples of safe attributes includes: align, alink, alt, bgcolor, border, cellpadding, cellspacing, class, color, cols, colspan, coords, dir, face, height, hspace, ismap, lang, marginheight, marginwidth, multiple, nohref, noresize, noshade, nowrap, ref, rel, rev, rows, rowspan, scrolling, shape, span, summary, tabindex, title, usemap, valign, value, vlink, vspace, width.

安全属性的示例包括:align、alink、alt、bgcolor、border、cellpadding、cellspacing、class、color、cols、colspan、coords、dir、face、height、hspace、ismap、lang、marginheight、marginwidth、multiple、nohref、noresize、noshade、nowrap、ref、rel、rev、rows、rowspan、scrolling、shape、span、summary、tabindex、title、usemap、valign、value、vlink、vspace、width。

GUIDELINE #4 - Avoid sending untrusted data into HTML rendering methods / 指南 #4 - 避免将不受信任的数据发送到HTML渲染方法

Avoid populating the following methods with untrusted data.

避免以下方法使用不受信任的数据填充。

  1. element.innerHTML = "...";
  2. element.outerHTML = "...";
  3. document.write(...);
  4. document.writeln(...);

GUIDELINE #5 - Avoid the numerous methods which implicitly eval() data passed to it / 指南 #5 - 避免使用隐式eval()传入数据的方法

There are numerous methods which implicitly eval() data passed to it that must be avoided.

有许多方法会隐式地eval()传入的数据,必须避免使用。

Make sure that any untrusted data passed to these methods is:

确保传递给这些方法的任何不受信任的数据是:

  1. Delimited with string delimiters
    中文:使用字符串分隔符分隔
  2. Enclosed within a closure or JavaScript encoded to N-levels based on usage
    中文:包含在闭包中或根据使用情况JavaScript编码至N级
  3. Wrapped in a custom function.
    中文:封装在自定义函数中。

Ensure to follow step 3 above to make sure that the untrusted data is not sent to dangerous methods within the custom function or handle it by adding an extra layer of encoding.

务必遵循上述第3步,以确保不受信任的数据不会发送到自定义函数中的危险方法,或者通过添加额外的编码层来处理它。

Utilizing an Enclosure (as suggested by Gaz) / 利用闭包(如Gaz建议的)

The example that follows illustrates using closures to avoid double JavaScript encoding.

以下示例说明了使用闭包来避免双重JavaScript编码。

 var ESAPI = require('node-esapi');
 setTimeout((function(param) { return function() {
          customFunction(param);
        }
 })("<%=ESAPI.encoder().encodeForJavascript(untrustedData)%>"), y);

The other alternative is using N-levels of encoding.

另一种选择是使用N级编码。

N-Levels of Encoding / 编码层级-N

If your code looked like the following, you would need to only double JavaScript encode input data.

如果你的代码看起来像下面这样,你只需要对JavaScript编码输入数据进行双重编码。

setTimeout("customFunction('<%=doubleJavaScriptEncodedData%>', y)");
function customFunction (firstName, lastName)
     alert("Hello" + firstName + " " + lastName);
}

The doubleJavaScriptEncodedData has its first layer of JavaScript encoding reversed (upon execution) in the single quotes.

doubleJavaScriptEncodedData的第一层JavaScript编码在单引号中(执行时)被反转。

Then the implicit eval of setTimeout reverses another layer of JavaScript encoding to pass the correct value to customFunction

然后setTimeout的隐式eval反转了另一层JavaScript编码,以将正确的值传递给customFunction

The reason why you only need to double JavaScript encode is that the customFunction function did not itself pass the input to another method which implicitly or explicitly called eval If firstName was passed to another JavaScript method which implicitly or explicitly called eval() then <%=doubleJavaScriptEncodedData%> above would need to be changed to <%=tripleJavaScriptEncodedData%>.

你只需要双重JavaScript编码的原因是customFunction函数本身没有将输入传递给另一个方法,该方法隐式或显式地调用了eval。如果firstName被传递给另一个隐式或显式调用eval()的JavaScript方法,那么上面的<%=doubleJavaScriptEncodedData%>需要更改为<%=tripleJavaScriptEncodedData%>。

An important implementation note is that if the JavaScript code tries to utilize the double or triple encoded data in string comparisons, the value may be interpreted as different values based on the number of evals() the data has passed through before being passed to the if comparison and the number of times the value was JavaScript encoded.

一个重要的实现注意事项是,如果JavaScript代码试图在字符串比较中使用双重或三重编码的数据,则值可能会根据数据在传递给if比较之前通过evals()的次数以及值被JavaScript编码的次数而被解释为不同的值。

If A is double JavaScript encoded then the following if check will return false.

如果A是双重JavaScript编码,那么下面的if检查将返回false。

 var x = "doubleJavaScriptEncodedA";  //\u005c\u0075\u0030\u0030\u0034\u0031
 if (x == "A") {
    alert("x is A");
 } else if (x == "\u0041") {
    alert("This is what pops");
 }

This brings up an interesting design point. Ideally, the correct way to apply encoding and avoid the problem stated above is to server-side encode for the output context where data is introduced into the application.

这提出了一个有趣的设计点。理想情况下,正确应用编码并避免上述问题的方法是,在数据被引入应用程序的输出上下文时,在服务器端进行编码。

Then client-side encode (using a JavaScript encoding library such as node-esapi) for the individual subcontext (DOM methods) which untrusted data is passed to.

客户端编码(使用如node-esapi这样的JavaScript编码库)对单个子上下文(DOM方法)进行编码,其中不可信数据被传递到这些方法中。

Here are some examples of how they are used:

以下是一些它们的使用示例:

//server-side encoding
var ESAPI = require('node-esapi');
var input = "<%=ESAPI.encoder().encodeForJavascript(untrustedData)%>";
//HTML encoding is happening in JavaScript
var ESAPI = require('node-esapi');
document.writeln(ESAPI.encoder().encodeForHTML(input));

One option is utilize ECMAScript 5 immutable properties in the JavaScript library. Another option provided by Gaz (Gareth) was to use a specific code construct to limit mutability with anonymous closures.

一个选项是利用JavaScript库中的ECMAScript 5不可变属性。另一个由Gaz(Gareth)提供的选项是使用特定的代码结构通过匿名闭包来限制可变性。

An example follows:

以下是一个示例:

function escapeHTML(str) {
     str = str + "''";
     var out = "''";
     for(var i=0; i<str.length; i++) {
         if(str[i] === '<') {
             out += '&lt;';
         } else if(str[i] === '>') {
             out += '&gt;';
         } else if(str[i] === "'") {
             out += '&#39;';
         } else if(str[i] === '"') {
             out += '&quot;';
         } else {
             out += str[i];
         }
     }
     return out;
}

GUIDELINE #6 - Use untrusted data on only the right side of an expression / 指南 #6 - 仅在表达式的右侧使用不可信数据

Use untrusted data on only the right side of an expression, especially data that looks like code and may be passed to the application (e.g., location and eval()).

仅在表达式的右侧使用不可信数据,特别是看起来像代码且可能传递给应用程序的数据(例如,location和eval())。

window[userDataOnLeftSide] = "userDataOnRightSide";

Using untrusted user data on the left side of the expression allows an attacker to subvert internal and external attributes of the window object, whereas using user input on the right side of the expression doesn't allow direct manipulation.

在表达式的左侧使用不可信用户数据允许攻击者篡改窗口对象的内部和外部属性,而使用用户输入在表达式的右侧则不允许直接操作。

GUIDELINE #7 - When URL encoding in DOM be aware of character set issues / 指南 #7 - 在DOM中进行URL编码时,注意字符集问题

When URL encoding in DOM be aware of character set issues as the character set in JavaScript DOM is not clearly defined (Mike Samuel).

当在 DOM 中进行 URL 编码时,要注意字符集问题,因为 JavaScript DOM 中的字符集并未明确定义(Mike Samuel)。

GUIDELINE #8 - Limit access to object properties when using object[x] accessors / 指南 #8 - 使用对象[x]访问器时限制对对象属性的访问

Limit access to object properties when using object[x] accessors (Mike Samuel). In other words, add a level of indirection between untrusted input and specified object properties.

使用 object[x] 访问器时限制对对象属性的访问(Mike Samuel)。换句话说,在不受信任的输入和指定的对象属性之间添加一层间接层。

Here is an example of the problem using map types:

以下是一个使用映射类型的示例问题:

var myMapType = {};
myMapType[<%=untrustedData%>] = "moreUntrustedData";

The developer writing the code above was trying to add additional keyed elements to the myMapType object. However, this could be used by an attacker to subvert internal and external attributes of the myMapType object.

编写上述代码的开发者试图向 myMapType 对象添加额外的键值元素。然而,这可能会被攻击者用来篡改 myMapType 对象的内部和外部属性。

A better approach would be to use the following:

更好的方法是使用以下方法:

if (untrustedData === 'location') {
  myMapType.location = "moreUntrustedData";
}

GUIDELINE #9 - Keep HTML sanitization separate from JavaScript execution / 指南 #9 - 将 HTML 清理与 JavaScript 执行分开

Use an HTML sanitizer such as DOMPurify when the application must render untrusted markup; see the HTML sanitization guidance. HTML sanitizers filter markup and do not provide a sandbox for executing arbitrary JavaScript. Do not pass untrusted code to eval() or Function().

当应用程序必须渲染不受信任的标记时,请使用 HTML 清理器,如 DOMPurify;参见 HTML 清理指南。HTML 清理器会过滤标记,并不提供执行任意 JavaScript 的沙箱。不要将不受信任的代码传递给 eval() 或 Function()(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#never_use_direct_eval!)。

GUIDELINE #10 - Don't eval() JSON to convert it to native JavaScript objects / 指南 #10 - 不要使用 eval() 转换 JSON 为原生 JavaScript 对象

Don't eval() JSON to convert it to native JavaScript objects. Use the built-in JSON.parse() to deserialize JSON into JavaScript values, and JSON.stringify() to serialize JavaScript values into JSON. JSON.parse() rejects anything that is not valid JSON, so it cannot execute attacker-supplied code the way eval() can.

不要使用 eval() 将 JSON 转换为原生 JavaScript 对象。使用内置的 JSON.parse() 将 JSON 反序列化为 JavaScript 值,并使用 JSON.stringify() 将 JavaScript 值序列化为 JSON。JSON.parse() 会拒绝任何非有效 JSON 的内容,因此它不能像 eval() 那样执行攻击者提供的代码。

[!WARNING]

中文:[!警告] JSON.stringify() is not an output-encoding function. Its output is valid JSON, but embedding it in HTML requires protection for the destination context. Prefer a separate response with Content-Type: application/json, parsed client-side with JSON.parse(). If JSON must be embedded in an inline script, use a serializer or encoder documented for that exact placement, protecting both JavaScript syntax and the enclosing HTML script context, including literal </script sequences. HTML entity encoding or JavaScript quote escaping alone is not sufficient for this placement. For other placements, follow the context-specific XSS encoding rules.

中文:JSON.stringify() 并不是一个输出编码函数。它的输出是有效的 JSON,但将其嵌入到 HTML 中需要保护目标上下文。优先使用带有 Content-Type: application/json 的单独响应,客户端使用 JSON.parse() 解析。如果必须在行内脚本中嵌入 JSON,请使用针对该特定位置记录的序列化器或编码器,保护 JavaScript 语法和 包围的 HTML 脚本上下文,包括字面量 </script 序列。仅使用 HTML 实体编码或 JavaScript 引号转义不足以保护此位置。对于其他位置,遵循 特定上下文的 XSS 编码规则。

Common Problems Associated with Mitigating DOM Based XSS / 与缓解基于 DOM 的 XSS 相关的常见问题

Complex Contexts / 复杂上下文

In many cases the context isn't always straightforward to discern.

在许多情况下,上下文并不总是容易辨别。

<a href="javascript:myFunction('<%=untrustedData%>', 'test');">Click Me</a>
 ...
<script>
Function myFunction (url,name) {
    window.location = url;
}
</script>

In the above example, untrusted data started in the rendering URL context (href attribute of an a tag) then changed to a JavaScript execution context (javascript: protocol handler) which passed the untrusted data to an execution URL subcontext (window.location of myFunction).

在上面的例子中,不受信任的数据从渲染 URL 上下文(a 标签的 href 属性)开始,然后变为 JavaScript 执行上下文(javascript: 协议处理程序),该上下文将不受信任的数据传递给执行 URL 子上下文(myFunction 的 window.location)。

Because the data was introduced in JavaScript code and passed to a URL subcontext the appropriate server-side encoding would be the following:

由于数据是在 JavaScript 代码中引入并传递到 URL 子上下文,适当的服务器端编码如下:

<a href="javascript:myFunction('<%=ESAPI.encoder().encodeForJavascript(ESAPI.encoder().encodeForURL(untrustedData)) %>', 'test');">
Click Me</a>
 ...

Or if you were using ECMAScript 5 with an immutable JavaScript client-side encoding libraries you could do the following:

如果您的项目使用的是 ECMAScript 5 以及不可变的 JavaScript 客户端编码库,您可以执行以下操作:

<!-- server side URL encoding has been removed.  Now only JavaScript encoding on server side. -->
<a href="javascript:myFunction('<%=ESAPI.encoder().encodeForJavascript(untrustedData)%>', 'test');">Click Me</a>
 ...
<script>
Function myFunction (url,name) {
    var encodedURL = ESAPI.encoder().encodeForURL(url);  //URL encoding using client-side scripts
    window.location = encodedURL;
}
</script>

Inconsistencies of Encoding Libraries / 编码库的不一致性

There are a number of open source encoding libraries out there:

市面上有许多开源的编码库:

  1. OWASP ESAPI
  2. OWASP Java Encoder
  3. Apache Commons Text StringEscapeUtils, replace one from Apache Commons Lang3
    中文:Apache Commons Text StringEscapeUtils,替换自 Apache Commons Lang3
  4. Jtidy
  5. Your company's custom implementation.
    中文:贵公司的自定义实现。

Some work on a denylist while others ignore important characters like "<" and ">".

一些库使用拒绝列表,而另一些库则忽略了重要的字符,如 "<" 和 ">"。

Java Encoder is an active project providing supports for HTML, CSS and JavaScript encoding.

Java Encoder 是一个活跃的项目,提供对 HTML、CSS 和 JavaScript 编码的支持。

ESAPI is one of the few which works on an allowlist and encodes all non-alphanumeric characters. It is important to use an encoding library that understands which characters can be used to exploit vulnerabilities in their respective contexts. Misconceptions abound related to the proper encoding that is required.

ESAPI 是少数几个基于允许列表进行编码的库之一,它会编码所有非字母数字字符。使用理解在各自上下文中哪些字符可以用来利用漏洞的编码库非常重要。关于正确编码的误解很多。

Encoding Misconceptions / 编码误解

Many security training curriculums and papers advocate the blind usage of HTML encoding to resolve XSS.

许多安全培训课程和论文提倡盲目使用HTML编码来解决XSS问题。

This logically seems to be prudent advice as the JavaScript parser does not understand HTML encoding.

从逻辑上讲,这似乎是谨慎的建议,因为JavaScript解析器不理解HTML编码。

However, if the pages returned from your web application utilize a content type of text/xhtml or the file type extension of *.xhtml then HTML encoding may not work to mitigate against XSS.

然而,如果你的Web应用返回的页面使用text/xhtml内容类型或*.xhtml文件类型扩展名,那么HTML编码可能无法有效地减轻XSS攻击。

For example:

例如:

<script>
&#x61;lert(1);
</script>

The HTML encoded value above is still executable. If that isn't enough to keep in mind, you have to remember that encodings are lost when you retrieve them using the value attribute of a DOM element.

上面的HTML编码值仍然可执行。如果这还不足以引起注意,你还必须记住,当你使用DOM元素的value属性检索编码时,编码会丢失。

Let's look at the sample page and script:

让我们看看示例页面和脚本:

<form name="myForm" ...>
  <input type="text" name="lName" value="<%=ESAPI.encoder().encodeForHTML(last_name)%>">
 ...
</form>
<script>
  var x = document.myForm.lName.value;  //when the value is retrieved the encoding is reversed
  document.writeln(x);  //any code passed into lName is now executable.
</script>

Finally there is the problem that certain methods in JavaScript which are usually safe can be unsafe in certain contexts.

最后还有这样一个问题,JavaScript中通常认为是安全的方法在某些上下文中可能是不安全的。

Usually Safe Methods / 通常安全的方法

One example of an attribute which is thought to be safe is innerText.

一个被认为是安全的属性示例是innerText。

Some papers or guides advocate its use as an alternative to innerHTML to mitigate against XSS in innerHTML. However, depending on the tag which innerText is applied, code can be executed.

一些论文或指南建议将其用作 innerHTML 的替代方案,以减轻 innerHTML 中的 XSS 攻击。然而,根据应用 innerText 的标签,代码可能会被执行。

<script>
 var tag = document.createElement("script");
 tag.innerText = "<%=untrustedData%>";  //executes code
</script>

The innerText feature was originally introduced by Internet Explorer, and was formally specified in the HTML standard in 2016 after being adopted by all major browser vendors.

innerText 功能最初由 Internet Explorer 引入,并在所有主要浏览器厂商采用后,于 2016 年正式在 HTML 标准中指定。

Detect DOM XSS using variant analysis / 使用变体分析检测 DOM XSS

Vulnerable code:

易受攻击的代码:

<script>
var x = location.hash.split("#")[1];
document.write(x);
</script>

Semgrep rule to identify above dom xss link.

Semgrep 规则用于识别上述 DOM XSS 链接。

References / 参考