Prototype Pollution: Poisoning JavaScript's Object Chain
How Prototype Pollution corrupts JavaScript's prototype chain and how to defend against it
The Document Object Model (DOM) represents the structure, content, and style of a web page in a way that allows JavaScript programs to manipulate it. Think of it as the bridge between your HTML content and the JavaScript that needs to interact with it. The DOM provides a structured representation of documents as a tree of objects that JavaScript can access and modify.
DOM Clobbering is an attack technique that abuses how browsers handle HTML elements with IDs and names. The term “clobbering” comes from software engineering, where it refers to intentionally or unintentionally overwriting a specific memory location or register.
In the context of web security, DOM Clobbering refers to the act of overwriting JavaScript objects by using specially crafted HTML elements. When a website uses window object properties or global variables, an attacker can “clobber” (override) these objects by injecting HTML elements with specific ID or name attributes.
This attack technique specifically exploits how JavaScript processes DOM elements, taking advantage of the browser’s automatic creation of global variables for elements with ID attributes. The term “clobbering” in this context literally means “overwriting the DOM,” as attackers can manipulate and override expected DOM behaviors.
A key aspect of DOM Clobbering is that HTML elements with ID attributes become accessible not only through document.getElementById() but also directly through the window object. For example:
<a id="CONFIG"></a>
This element can be accessed in two ways:
document.getElementById('CONFIG');
window.CONFIG;
This behavior isn’t limited to exact matches between document objects and variable names. Overwriting can occur even when a variable with the same name exists in the global scope. Here’s an illustrative example:
<a id="testzz" href="/111">
<script>
alert(window.testzz)
</script>
<!-- This will output: /111 -->
In this case, the href attribute value becomes accessible through the window object, demonstrating how DOM Clobbering can affect global variable scope.
The fundamental principle of DOM Clobbering is based on how browsers handle HTML elements with ID attributes. When you create an HTML element with an ID attribute, the browser automatically creates a global variable with the same name, accessible via the window object.
For example, when you have this HTML:
<a id="CONFIG"></a>
You can access this element in JavaScript using either of these methods:
document.getElementById('CONFIG');
window.CONFIG;
This behavior is what makes DOM Clobbering possible. If a website’s JavaScript code relies on a global variable like window.CONFIG, and an attacker can inject HTML with an ID of “CONFIG”, they can override the expected object with their HTML element.
Let’s look at a basic example that demonstrates DOM Clobbering:
<html>
<body>
<a id="CONFIG"></a>
<a id="CONFIG" name="cmd" href="javascript:alert(secretkey)"></a>
</body>
<script>
secretkey = "sensitive_data";
window.CONFIG = window.CONFIG || {
cmd: "1",
isadmin: false,
};
if (window.CONFIG.isadmin !== false) {
location.href = window.CONFIG.cmd;
}
</script>
</html>
In this example, the JavaScript code expects window.CONFIG to be an object with properties like cmd and isadmin. However, the attacker has injected two anchor tags with the ID “CONFIG”, which creates an HTML Collection. When the script tries to access window.CONFIG.cmd, it’s actually accessing the href attribute of the second anchor tag, which contains malicious JavaScript.
Forms are particularly susceptible to DOM Clobbering because browsers treat form elements with name and id attributes as properties of the Form object. This can lead to unexpected behavior when accessing form methods.
Consider this example:
<html>
<form action="">
<input type="text" value="aaa" />
<input type="submit" name="submit" />
</form>
<script>
document.forms[0].submit();
</script>
</html>
This code will generate an error: Uncaught TypeError: document.forms[0].submit is not a function.
Why? Because the input element with name="submit" has overwritten the form’s built-in submit() method. Instead of getting the function, you’re getting the input element itself.
Even more dangerously, attackers can clobber core DOM methods like getElementById:
<html>
<body>
<form name="getElementById"></form>
<div id="intadd">hihi</div>
</body>
<script>
document.getElementById("intadd");
</script>
</html>
This will result in Uncaught TypeError: document.getElementById is not a function because the form named “getElementById” has clobbered the document’s method.
HTML Collections play a key role in DOM Clobbering attacks, especially when combined with string conversion behavior of different HTML elements. Let’s look at this concept with some examples.
First, let’s look at a basic DOM Clobbering scenario using forms:
<html>
<body>
<form name="CHECKER">
<input name="isadmin" />
</form>
</body>
<script>
window.CHECKER = window.CHECKER || {
isadmin: false,
};
window.CONFIG = window.CONFIG || {
secretkey: "strooongkeeey",
isadmin: false,
};
if (window.CHECKER.isadmin || window.CONFIG.isadmin !== false) {
document.write(window.CONFIG.secretkey);
}
</script>
</html>
A key aspect of DOM Clobbering is understanding how different HTML elements convert to strings. For instance, anchor (<a>) tags have a specific behavior where their toString() method returns the value of their href attribute. However, note that anchor tags cannot be used as form element attributes.
To work around this limitation, we can use HTML Collections. When multiple elements share the same ID, they form an HTML Collection. Here’s an example that demonstrates this technique:
<html>
<body>
<a id="CONFIG"></a>
<a id="CONFIG" name="cmd" href="javascript:alert(secretkey)"></a>
</body>
<script>
secretkey = "asdfasdf";
window.CONFIG = window.CONFIG || {
cmd: "1",
isadmin: false,
};
if (window.CONFIG.isadmin !== false) {
location.href = window.CONFIG.cmd;
}
</script>
</html>
This example demonstrates how HTML Collections can be used to manipulate location.href and execute JavaScript code when the application uses window objects. The key points to remember are:
toString() method returns their href valuelocation.href in combination with window object usageTo protect your web applications from DOM Clobbering attacks:
Use namespaced objects - Keep your objects in namespaces that aren’t likely to be clobbered.
Strict property checking - Always use strict checks (=== instead of ==) and explicit property existence checks.
Input sanitization - Properly sanitize user inputs that could end up in the DOM.
Modern JavaScript practices - Use const and let instead of global variables, employ strict mode, and use modules.
Content Security Policy (CSP) - Implement a strong CSP to prevent the execution of unauthorized JavaScript.
DOM Clobbering represents a subtle but powerful attack vector that exploits the way browsers handle HTML elements with ID and name attributes. Understanding this vulnerability is key for web developers and security professionals to build safer web applications.
While DOM Clobbering may not be as widely known as other web vulnerabilities like XSS or CSRF, it can be just as dangerous, especially in environments where direct script injection is prevented but HTML injection is still possible.
By understanding the mechanisms behind DOM Clobbering and implementing proper defenses, developers can significantly reduce the risk of these attacks in their applications.
Document Object Model(DOM)은 웹 페이지의 구조, 콘텐츠, 스타일을 자바스크립트가 조작할 수 있는 형태로 표현한 것입니다. HTML 콘텐츠와, 그걸 주물러야 하는 자바스크립트 사이를 이어 주는 다리라고 생각하면 편한데요. DOM은 문서를 객체들의 트리로 구조화해서, 자바스크립트가 마음껏 접근하고 수정할 수 있게 해줍니다.
DOM Clobbering은 브라우저가 id, name 속성을 가진 HTML 요소를 다루는 방식을 악용하는 공격 기법입니다. “clobbering”이라는 말은 원래 소프트웨어 공학에서 특정 메모리 위치나 레지스터를 의도적이든 실수든 덮어쓰는 걸 가리키는 용어인데요.
웹 보안에서 DOM Clobbering은, 특수하게 만든 HTML 요소로 자바스크립트 객체를 덮어써 버리는 걸 뜻합니다. 웹사이트가 window 객체 프로퍼티나 전역 변수를 쓰고 있다면, 공격자는 특정 id, name 속성을 가진 HTML 요소를 슬쩍 끼워 넣어 그 객체를 “clobber”(덮어쓰기)할 수 있죠.
이 기법이 노리는 건 특히 자바스크립트가 DOM 요소를 처리하는 방식입니다. 브라우저가 id 속성을 가진 요소마다 같은 이름의 전역 변수를 자동으로 만들어 준다는 점을 파고드는 거예요. 여기서 “clobbering”은 말 그대로 “DOM을 덮어쓴다”는 의미로, 공격자가 원래 기대되던 DOM 동작을 제 입맛대로 바꿔치기할 수 있다는 뜻입니다.
DOM Clobbering의 핵심은, id 속성을 가진 HTML 요소가 document.getElementById()뿐 아니라 window 객체로도 곧장 접근된다는 점입니다. 예를 들어 볼까요?
<a id="CONFIG"></a>
이 요소는 두 가지 방법으로 접근할 수 있습니다.
document.getElementById('CONFIG');
window.CONFIG;
이 동작은 문서 객체와 변수 이름이 딱 일치할 때만 일어나는 게 아닙니다. 같은 이름의 변수가 전역 스코프에 있어도 덮어쓰기가 일어날 수 있는데요. 다음 예를 볼게요.
<a id="testzz" href="/111">
<script>
alert(window.testzz)
</script>
<!-- 출력 결과: /111 -->
보다시피 href 속성값이 window 객체를 통해 튀어나오죠. DOM Clobbering이 전역 변수 스코프에까지 어떻게 손을 뻗치는지 잘 보여주는 예입니다.
DOM Clobbering의 근본 원리는 결국 브라우저가 id 속성을 가진 HTML 요소를 다루는 방식에 있습니다. id 속성을 가진 HTML 요소를 만들면, 브라우저가 같은 이름의 전역 변수를 자동으로 만들어 주고, 이건 window 객체로 접근되거든요.
예를 들어 다음 HTML이 있다고 해볼게요.
<a id="CONFIG"></a>
자바스크립트에서 다음 두 방법 중 어느 쪽으로든 이 요소에 접근할 수 있습니다.
document.getElementById('CONFIG');
window.CONFIG;
바로 이 동작이 DOM Clobbering의 문을 엽니다. 웹사이트의 자바스크립트가 window.CONFIG 같은 전역 변수에 기대고 있는데, 공격자가 id가 “CONFIG”인 HTML을 끼워 넣을 수 있다면? 원래 있어야 할 객체를 자기 HTML 요소로 덮어쓸 수 있는 거죠.
백문이 불여일견, 기본 예시부터 봅시다.
<html>
<body>
<a id="CONFIG"></a>
<a id="CONFIG" name="cmd" href="javascript:alert(secretkey)"></a>
</body>
<script>
secretkey = "sensitive_data";
window.CONFIG = window.CONFIG || {
cmd: "1",
isadmin: false,
};
if (window.CONFIG.isadmin !== false) {
location.href = window.CONFIG.cmd;
}
</script>
</html>
이 코드는 window.CONFIG가 cmd, isadmin 같은 프로퍼티를 가진 객체이길 기대하고 있습니다. 그런데 공격자가 id가 “CONFIG”인 앵커 태그를 두 개 끼워 넣어 HTML Collection을 만들어 버렸죠. 그러면 스크립트가 window.CONFIG.cmd에 접근하는 순간, 실제로는 두 번째 앵커 태그의 href 속성을 집어 오게 되는데, 거기엔 악성 자바스크립트가 담겨 있죠.
폼은 DOM Clobbering에 특히 약합니다. 브라우저가 name, id 속성을 가진 폼 요소를 Form 객체의 프로퍼티로 취급하기 때문인데요. 그래서 폼 메서드에 접근하려다 예기치 못한 동작이 튀어나오기도 합니다.
다음 예시를 볼게요.
<html>
<form action="">
<input type="text" value="aaa" />
<input type="submit" name="submit" />
</form>
<script>
document.forms[0].submit();
</script>
</html>
이 코드는 Uncaught TypeError: document.forms[0].submit is not a function 오류를 뱉습니다.
왜 그럴까요? name="submit"인 input 요소가 폼의 내장 submit() 메서드를 덮어써 버렸기 때문입니다. 함수를 부르려는데 정작 손에 잡히는 건 input 요소 자체인 거죠.
더 아찔한 건, 공격자가 getElementById 같은 핵심 DOM 메서드까지 clobber할 수 있다는 점입니다.
<html>
<body>
<form name="getElementById"></form>
<div id="intadd">hihi</div>
</body>
<script>
document.getElementById("intadd");
</script>
</html>
이 코드는 Uncaught TypeError: document.getElementById is not a function 오류를 냅니다. “getElementById”라는 이름의 폼이 document의 메서드를 clobber해 버렸기 때문이죠.
HTML Collection은 DOM Clobbering 공격에서 아주 중요한 역할을 합니다. 특히 서로 다른 HTML 요소의 문자열 변환 동작과 맞물릴 때 진가를 발휘하는데요. 몇 가지 예시로 하나씩 짚어 보겠습니다.
먼저 폼을 이용한 기본 시나리오부터 볼게요.
<html>
<body>
<form name="CHECKER">
<input name="isadmin" />
</form>
</body>
<script>
window.CHECKER = window.CHECKER || {
isadmin: false,
};
window.CONFIG = window.CONFIG || {
secretkey: "strooongkeeey",
isadmin: false,
};
if (window.CHECKER.isadmin || window.CONFIG.isadmin !== false) {
document.write(window.CONFIG.secretkey);
}
</script>
</html>
여기서 핵심은 서로 다른 HTML 요소가 어떻게 문자열로 변환되는지를 이해하는 겁니다. 예컨대 앵커(<a>) 태그는 toString()이 href 속성값을 돌려주는 독특한 동작을 갖고 있는데요. 다만 앵커 태그는 폼 요소의 속성으로는 못 쓴다는 점에 주의해야 합니다.
이 제약은 HTML Collection으로 우회할 수 있습니다. 여러 요소가 같은 id를 공유하면 HTML Collection을 이루거든요. 이 기법을 보여주는 예시입니다.
<html>
<body>
<a id="CONFIG"></a>
<a id="CONFIG" name="cmd" href="javascript:alert(secretkey)"></a>
</body>
<script>
secretkey = "asdfasdf";
window.CONFIG = window.CONFIG || {
cmd: "1",
isadmin: false,
};
if (window.CONFIG.isadmin !== false) {
location.href = window.CONFIG.cmd;
}
</script>
</html>
이 예시는 애플리케이션이 window 객체를 쓸 때, HTML Collection으로 location.href를 조작해 자바스크립트 코드를 실행시키는 방법을 보여줍니다. 정리하면 이렇습니다.
toString()은 href 값을 돌려준다location.href 같은 프로퍼티를 노릴 때 특히 잘 먹힌다웹 애플리케이션을 DOM Clobbering으로부터 지키려면 이런 것들을 챙기면 됩니다.
네임스페이스 객체 사용: 객체를 clobber되기 어려운 네임스페이스 안에 넣어 두세요.
엄격한 프로퍼티 검사: 항상 엄격 비교(== 대신 ===)와 명시적인 프로퍼티 존재 검사를 씁니다.
입력 정제: DOM에 흘러 들어갈 수 있는 사용자 입력을 제대로 정제합니다.
현대적 자바스크립트 관행: 전역 변수 대신 const, let을 쓰고, strict mode와 모듈을 활용하세요.
콘텐츠 보안 정책(CSP): 인가되지 않은 자바스크립트 실행을 막도록 강력한 CSP를 적용합니다.
DOM Clobbering은 브라우저가 id, name 속성을 가진 HTML 요소를 다루는 방식을 파고드는, 미묘하지만 은근히 강력한 공격 벡터입니다. 그래서 이 취약점을 이해해 두는 건 웹 개발자와 보안 담당자가 더 안전한 애플리케이션을 만드는 데 큰 도움이 되는데요.
DOM Clobbering은 XSS나 CSRF만큼 유명하진 않지만, 직접적인 스크립트 주입은 막혀 있어도 HTML 주입은 여전히 통하는 환경이라면 그에 못지않게 위험할 수 있습니다.
메커니즘을 제대로 이해하고 적절한 방어를 갖춰 두면, 이런 공격의 위험을 크게 줄일 수 있을 거예요.