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)//