Cross-Site Request Forgery (CSRF): CSRF exploitation

Ultimate Guide into Finding and Exploiting Cross-Site Request Forgery Vulnerabilities

Ultimate Guide into Understanding and Identifying Cross-Site Request Forgery Vulnerabilities

Today's estimated reading time is: 13 min

Last week we saw a brief introduction to cross-site request forgery

In this post, I will show you almost all methods to exploit CSRF vulnerabilities with some common bypasses!

Today's post is brought to you by Nova Security

The cloud-based attack surface management & vulnerability detection platform that finds vulnerabilities before you do.

Waitlist is open, join today ===> https://novasec.io/

Basic CSRF Exploitation:

So we’ve seen what cross-site request forgery issues are

And although it might be still a bit vague for you, today’s post should take care of that as we are going to look more into the exploitation part

Basic CSRF exploitation involves you making a request that causes an action that modifies data on behalf of an authenticated user’s account

And a simple proof of concept may look like this:

https://example.com/messages/compose?receiver=account2%40example.com&message=test&send=1

The above PoC is a simple GET request made to the /messages/compose endpoint and it will send a message on behalf of our first test account to our second test account ([email protected]) with the message “test”

In a real-world attack scenario, an attacker might:

  1. send this as a link to the victim through social engineering

  2. include this in the vulnerable web app as an iframe, image src, link, etc as long as the SameSite cookie doesn’t interfere (everyone who visits that page with the iframe/image/… or clicks on the link, will send the message)

  3. or send a link to an attacker-controlled page that auto-submits an HTML form (more on this later)

CSRF Exploitation bypasses:

Method-based CSRF:

We’ve seen what a basic CSRF PoC can look like however chances of finding such simple cases are very rare

And GET requests are almost never used to perform an action so developers usually fall back to using a POST/PUT/PATCH request

The thing here is, they sometimes forget to remove other accepted HTTP methods or are just unaware of the capabilities of the technology they use (CMS, framework, library to parse requests, …)

And this can open a gap for us to still exploit CSRF by changing the request method, let’s look at an example below:

In the image above, we can see a PUT request getting sent to the API

And we can’t just send a PUT request using an HTML form (not possible) or Ajax request without triggering CORS (another browser security feature that allows you to declare what resources are allowed to be shared on other origins)

But on further inspection, you can notice that you can change the request method to GET and supply all the body parameters in the URL query

We can easily send this link and it would send the message on the victim’s behalf:

Some frameworks like Laravel and Ruby on Rails support parameters or request headers that allow you to override the HTTP method!

This means sending a request like /api/xyz?_method=POST will send a GET request but will be parsed as a POST request on the server-side!

On Ruby on Rails you can use the X-HTTP-Method-Override: GET request header, on Laravel: _method=GET

TIP!

Content-Type-based CSRF:

Most REST APIs only make use of JSON data in requests

And thing is, if you try to replicate the request using an HTML form, you’ll notice that it is not possible to do so using application/json content-type

Even if you try using an AJAX request, a pre-flight request will be made and will block you because of Cross-Origin Resource Sharing (CORS)

In this scenario, you’d need to try and enumerate any other content types that the endpoint may accept

For example, changing application/json request body to application/x-www-form-urlencoded may look like this:

This would enable us to still perform the CSRF using an HTML form as no CSRF token is used (because developers initially thought that using JSON is sufficient to mitigate CSRF):

In some cases, you may notice that only a JSON object is allowed but any content type is accepted

In this case, you’d want to make use of the text/plain content-type inside an HTML form:

In the image above, we need to split up the JSON object to make it fit in an HTML form, typically, it’ll add a = between the name and value of the parameter

We fixed this by inserting it in the message field

Lack of CSRF token validation:

I’ve seen cases where a CSRF token is present (in the request header or as a parameter) but isn’t validated at all

This opens up a lot of possibilities for us, for example, we could:

  • Remove the CSRF parameter or header completely and send the request

  • Change it with a guessable value like “1” or “batman” or anything else

  • Change it with another value as long as it is the same length as the original CSRF token

  • Change it with another CSRF token (one of your second account or a previously generated CSRF token)

To provide some more context on the last case: this is usually because the CSRF token is not tied to the user’s session

Instead, developers decided to add all generated tokens (that are active) and save them in a database

And that means that any generated token can be used by any user (as long as the token is present in the database)

Some developers assume that CSRF tokens can’t be modified and often don’t escape it whenever they reflect it in the response (when an invalid token is sent for example)

I found an XSS some time ago, so make sure you check for it too (and don’t limit yourself, try to transform request headers into query/body parameters to get a working proof of concept 😉)!

TIP!

Cookie-based CSRF:

Another approach that some developers take to mitigate CSRF attacks is generating the token and saving it in a cookie + supplying it in the request body

And on the server side, both values get compared

If both tokens match, then the action is allowed. Otherwise, your request gets rejected

Now, this approach is generally not recommended as you can create a cookie yourself using a CR/LF injection (or some other function that allows you to set cookies) and supply the same token in the body as well, like in the image below:

To further explain what’s happening here is:

  1. First of all, we make use of a CRLF Injection to inject the Set-Cookie: CSRF_TOKEN={CSRF_TOKEN} in the response body (the CSRF token can be a previously generated one or any token that has the same character length). This will set the cookie for us and we use an image src for it as a simple GET request is required

  2. Next, it will submit the request using the same token as the one set in the cookie

Login/logout CSRF:

You’ll often notice that you can log yourself out by visiting an endpoint or a route, for example /account/logout

This is called “Logout CSRF”, the same as when logging in via OAuth for example (when you opt-in for your credentials to be saved), this CSRF issue is called “Login CSRF”

Login/Logout CSRF are generally not considered to be an issue in bug bounty as all that matters is the impact — which is none at the moment (unless otherwise mentioned by the program)

However, this bug is perfect for chaining it with a stored self-XSS!

I won’t go into depth here but I will link down an article that goes over this attack scenario

The lesson here is that “useless CSRFs” are not always “useless”, sometimes you’ll need to chain multiple bugs together to achieve the most impact

Referer header CSRF:

I personally haven’t encountered this before but apparently, there are developers that check if a request is valid if the referer header is matched

And the way to exploit this is by trying to match the loosely set regex pattern

For instance, you could try:

  • and make sure the target’s domain is present in the URL query parameter or path: https://attacker.com/target.com/x?target.com

  • or try to use the target’s domain name as the subdomain: https://target.com.attacker.com/x

Sometimes there’s no handling done at all when the referer header is not set

In that case, it is always a good idea to remove it from the request by setting the following tag in the head element of your proof of concept:

<meta name="referrer" content="no-referrer">

I recommend trying out all different methods and combining these (such as change content-type + request method)!

I also recommend using a CSRF exploit generator to easily create proof of concepts. Burpsuite has a built-in one but only if you have the professional version. I’m sure that ZAProxy has it as well

TIP!

Resources worth reading:

If you're interested in levelling up your bug bounty game, I can help you:

  • Get $200 in Digital Ocean credits to set up your Virtual Private Server for recon:

  • Help automate your vulnerability and recon scans with less effort using Nova Security:

Thank you for reading this far!

I hope you've enjoyed & learned something new from this post!

If you have any feedback, please do not hesitate to reach out! You can reply to this email directly or send me via Twitter DM!

Have a nice day and see you in the next post!

You can follow me on Twitter to receive upcoming updates on this newsletter: