The solution to the "Hard" challenge is...
<img name=notify><style><style/><script>alert(1337)//
That's it, but there's quite a bit of stuff going on behind the scenes, lemme explain.
The sink here is Jquery’s
html()
function. Now you might usually ignore this as a sink because there’s DOMPurify sanitizing our input right? Well yes, but also no, not in the right context, because we’ve made a false assumption that sanitizing
innerHTML
is the same as sanitizing Jquery’s
html()
, which is not the case. Jquery does use
innerHTML
behind the scenes, but it does a little bit of fancy work before inserting it to the DOM.
Here's a rough call trace when the payload hits Jquery's
html()
Function.
html()
append()
domManip()
buildFragment()
htmlPrefilter()
Here, the interesting part is
htmlPrefilter()
.
// source of htmlPrefilter()
jQuery.extend( {
htmlPrefilter: function( html ) {
return html.replace( rxhtmlTag, "<$1></$2>" );
},
...
This essentially converts a self-closing tag into a full blown tag, for example,
<blah/>
<!-- converted to -->
<blah></blah>
This can be really powerful.
Consider
<style><style/>Elon
, when
innerHTML
is used to insert this into the DOM, the resulting DOM Tree looks like this.
<style>
<style/>Elon
</style>
But with jquery’s
html()
, it’s a whole different story. When we try the same input with
html()
, we see the following.
<style>
<style>
</style>
Elon
The self-closing
<style/>
is replaced with
<style></style>
, which makes the second
<style>
tag to be treated as the contents of the first
<style>
tag, but look what happened to the
Elon
text, it’s outside the
<style>
tag and out open in the HTML context. Ergo, XSS.
But we still have one more problem, that’s the notification. The application uses Jquery’s
html()
only when the
notify
variable is
true
, but it’s clearly
false
, so how do we set it to
true
?
With the help of DOM clobbering and augmented scope chain, we can bypass this. DOM clobbering is basically creating html elements with id or sometimes name attributes can create javascript variables with that same name, I’ve explained it a bit more in the
Ok, Boomer challenge or check out this
YouTube Video.
But the problem is, we can’t overwrite the existing variables with this technique, because browsers don’t let you do that, that would be kinda crazy. So how are we supposed to change the
notify
value to
true
when we can’t overwrite the value? Well we can do some ninja shadow tricks.
When an element has an event handler attribute on it like in our example,
onload
is the event. When the image loads, whatever the code is defined as the value, gets executed, basic stuff. But the code that runs when the event is fired, is actually wrapped inside a function that is then later called by the browser when the event is actually fired. Along with this, browsers augment the function’s scope chain with the element itself,
form
(if there's one) and also with the
document
object. Now if you have played around with DOM clobbering a bit, you’d know that you can clobber any property of the
document
object with HTML elements that can clobber using the
name
attribute, For example
<img name=domain>
, will clobber
document.domain
.
This means we can create a property named
notify
on the
document
object. Why would we need that? As we already know that there’s augmented scope on the
document
object on event handler code that’s wrapped in a function. Which means now the the javascript interpreter will check if
notify
is defined in the local scope, which is not the case, so it goes up the scope chain and checks in the
document
object, and here we do have a property named
notify
, so it’ll pull that value from
document
object instead of pulling it from the global scope. So we’ve essentially shadowed the original.
Combining both the techniques, we can solve the challenge.
img=valid_image_url&text=<img name=notify><style><style/><script>alert(1337)//