15

I am testing web application for which business actions are done by sending JSON requests like for example:

POST /dataRequest HTTP/1.1
Host: test.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:55.0) Gecko/20100101 
Firefox/55.0
Accept: */*
Accept-Language: pl,en-US;q=0.7,en;q=0.3
Content-Type: application/json; charset=utf-8
Content-Length: 99
Cookie: SESSIONID=7jtyutuytu1a
Connection: close

{"F":"test.AppRequestFactory","I":[{"O":"5vhghgjhgjE0="}]}

I made the HTML auto-submit page like this

<html>
<head>
</head>
<body onload=document.getElementById('xsrf').submit()>
    <form id="xsrf" action="https://test.com/dataRequest" method=post enctype="text/plain">
    <input name='{"F":"test.AppRequestFactory","I":[{"O":""O":"5vhghgjhgjE0' value='"}]}' type='hidden'>
    </form>
</body>
</html>

The problem is that it will be send with header Content-Type: text/plain, but the server only accepts Content-Type: application/json; charset=utf-8.

I've read the discussion CSRF with JSON POST where one of the comments states:

Use something like this: var blob= new Blob([JSON.stringify(YOUR JSON)], {type : 'application/json; charset=UTF-8'}); to generate a JSON blob and it'll send perfectly. CSRF in seconds!

But I have no idea how to use this approach.

Is this application vulnerable to CSRF attack?

Anders
  • 64,406
  • 24
  • 178
  • 215
user187205
  • 1,163
  • 3
  • 15
  • 24
  • None of what you've posted looks relevant to xsrf. Are you even using cookie auth? What happens if you set the encType to application/json? Have you looked at any of the mitigation here. https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet – ste-fu Oct 01 '17 at 19:33

3 Answers3

11

Is this application vulnerable to CSRF attack?

Yes, it's vulnerable. The prerequisite, however, here is Flash. With the help of Flash, it's possible to forge a Content-type header with any arbitrary value. What you need to do is POST a request to your own domain, and then issue a 307 redirect. Please refer to below screenshot:

x-domain application/json request

For more information, please refer to this cm2.pw article.

Use something like this: var blob= new Blob([JSON.stringify(YOUR JSON)], {type : 'application/json; charset=UTF-8'}); to generate a JSON blob and it'll send perfectly. CSRF in seconds!

This, afaik, is already fixed in modern browsers. However, it still works in IE with file URI.

Update:
Since it's still getting a lot of views lately, I'm adding links to fixes; https://bugzilla.mozilla.org/show_bug.cgi?id=1436241 https://bugs.chromium.org/p/chromium/issues/detail?id=332023

For some reason, chromium fix does not work and still allow sending POST request with custom Content-type headers.

1lastBr3ath
  • 909
  • 6
  • 13
  • This is a great point that I completely missed in my answer. Thanks for pointing it out! – Anders Jan 04 '18 at 19:58
  • 1
    @Anders What's still unclear to me - isn't this Flash behavior fixed by now? – Arminius Jan 04 '18 at 20:09
  • @Arminius Don't know, but there is still old Flash and old browsers out there. I think that is enough to merit swift action for OP. – Anders Jan 04 '18 at 20:13
  • 1
    @Arminius It's not. Only the behavior allowing to set arbitrary HTTP Headers was fixed. – 1lastBr3ath Jan 05 '18 at 02:47
  • Another example about the Flash CSRF attack can be found in [a blog by Appsecco](https://blog.appsecco.com/exploiting-csrf-on-json-endpoints-with-flash-and-redirects-681d4ad6b31b); source code available [on GitHub](https://github.com/appsecco/json-flash-csrf-poc). – Franklin Yu Sep 05 '19 at 20:21
8

Warning: This answer may be wrong and to optimistic. See 1lastBr3ath's answer above (concerning flash) and jub0bs comment below (concerning a clever workaround).

No, I don't think the application is vulnerable.

You can change the Content-Type header, e.g. using the fetch API. However, there are only three values that you can use for cross domain requests:

application/x-www-form-urlencoded
multipart/form-data
text/plain

If you change it to anything else, such as application/json, the browser will first make an OPTIONS request to server, to see if it allows that header to be changed. This behaviour is part of CORS, and it is designed to limit what cross domain requests you can make with JavaScript to the old fashioned ones you could do with simple HTML. So unless the server specifically allows any domain to set this header (which would be a stupid thing to do), you are out of luck.

Note, however, that this seems to be a case of "security by accident". I would rely on something stronger for my CSRF protection (and perhaps they do, once you get past the content type hurdle). What happends if someday someone thinks it would be nice if the server accepts other content types and removes that limitation? With this configuration, it would be easy to accidentally open up a security hole.

Anders
  • 64,406
  • 24
  • 178
  • 215
  • 2
    *However, there are only three values [...]* CORS is actually more permissive than meets the eye. In particular, it breaks some pre-CORS assumptions about the possible `Content-Type` values that a simple request can carry. For instance, browsers will happily send a no-CORS request with content type `text/plain; application/json`; see https://untruetauttriangle.jub0bs.repl.co/. If the server only checks whether `application/json` is a substring of the `Content-Type` header value, the attack would be successful. – jub0bs Aug 30 '21 at 09:28
  • 1
    @jub0bs That is very interesting - I did not know that. Will read up on this, sounds fashinating. Updated the warning at the top. – Anders Aug 31 '21 at 16:49
  • 3
    For more about this, see section 4.1 in https://www.usenix.org/system/files/conference/usenixsecurity18/sec18-chen.pdf – jub0bs Sep 01 '21 at 12:38
0

An another way to achieve this is by taking advantage of the fact that most of json parsers respect the use of comments. So by creating a simple html form with a hidden input, you can put the json data as name of the input element in order to get it posted to the body. In this senario the only problem is that when the form is submitted the body of the post will have the '=' (from the input's name=value format). So to avoid that and make the json valid again u can add a comment indicator at the end of the name (your json data). This way the '=' character will be commented when parsed.

Here is an example:

<html>
  <body>
  <script>history.pushState('', '', '/')</script>
    <form action="https://www.example.com/" method="POST" enctype="text/plain">
      <input type="hidden" name='{"name":"value"}//' value="" />
    </form>
    <script>
      document.forms[0].submit();
    </script>
  </body>
</html>
cavla
  • 1
  • The question was not about making the parser validate the json but how to send it such as the browser set the `Content-Type: application/json` header in the request. I've tested your solution and despite being valid json and having set enctype to `application/json` it still send it as `text/plain`. – Xavier59 Jan 08 '19 at 08:40
  • There are no comments in JSON, it should be: `````` or, `````` Reference: https://www.gracefulsecurity.com/csrf-vs-json/ – aDoN Sep 17 '19 at 14:40