
Cross-Site Request Forgery (CSRF) Attacks
Understanding CSRF vulnerabilities and implementing effective protection mechanisms
Table of Contents
Cross-Site Request Forgery (CSRF) Attacks
Cross-Site Request Forgery (CSRF) is a type of attack that tricks a user’s browser into executing unwanted actions on a website where the user is authenticated. Unlike XSS, which exploits the trust a user has in a particular site, CSRF exploits the trust that a site has in a user’s browser.
How CSRF Attacks Work
CSRF attacks work by taking advantage of websites that rely solely on session cookies to authenticate users. When a user is authenticated to a site, their browser automatically sends the session cookie with every request to that site. A CSRF attack tricks the victim’s browser into sending a malicious request to the vulnerable website.
Attack Scenario
- User logs into
bank.com
and receives a session cookie - Without logging out, the user visits
malicious.com
malicious.com
contains code that submits a form tobank.com/transfer
- The browser automatically includes the user’s
bank.com
cookies with the request bank.com
processes the request as legitimate since it includes valid session cookies
Example CSRF Attack
HTML Form-based Attack
<!-- On malicious.com -->
<html>
<body>
<form id="csrf-form" action="https://bank.com/transfer" method="POST">
<input type="hidden" name="recipient" value="attacker" />
<input type="hidden" name="amount" value="1000" />
</form>
<script>
document.getElementById("csrf-form").submit();
</script>
</body>
</html>
Image-based Attack
<img src="https://bank.com/transfer?recipient=attacker&amount=1000" width="0" height="0" />
XMLHttpRequest Attack
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://bank.com/transfer", true);
xhr.withCredentials = true; // This is needed to include cookies
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send("recipient=attacker&amount=1000");
CSRF Prevention Techniques
1. CSRF Tokens
The most effective defense is to include a unique, unpredictable token with each request that requires protection:
<form action="/transfer" method="post">
<input type="hidden" name="csrf_token" value="random_token_tied_to_user_session" />
<input type="text" name="recipient" />
<input type="number" name="amount" />
<button type="submit">Transfer</button>
</form>
Server-side validation:
# Python/Flask example
@app.route('/transfer', methods=['POST'])
def transfer():
if request.form['csrf_token'] != session['csrf_token']:
abort(403) # Forbidden
# Process the transfer
2. Same-Site Cookies
Set the SameSite
attribute on cookies to restrict when they are sent:
Set-Cookie: sessionid=abc123; SameSite=Strict; Secure; HttpOnly
Strict
: Cookies are only sent in a first-party contextLax
: Cookies are sent when the user navigates to the site from an external linkNone
: Cookies are sent in all contexts (requiresSecure
attribute)
3. Custom Request Headers
For AJAX requests, leverage the Same-Origin Policy by adding a custom header that can only be set by JavaScript from the same origin:
// JavaScript
fetch('/api/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({ recipient: 'friend', amount: 100 })
});
Server-side validation:
// Node.js example
if (req.headers['x-requested-with'] !== 'XMLHttpRequest') {
return res.status(403).send('Forbidden');
}
4. Double Submit Cookie
Set a cookie with a random value and include the same value as a hidden field or request parameter:
// Set the cookie
document.cookie = "csrfCookie=random_value; SameSite=Strict; Secure";
// Include the value in the request
fetch('/api/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': 'random_value'
},
body: JSON.stringify({ recipient: 'friend', amount: 100 })
});
5. Require User Interaction
For sensitive operations, require additional user interaction:
- Re-authentication for sensitive actions
- CAPTCHA challenges
- Confirmation steps
CSRF in Modern Web Applications
Modern web frameworks typically include built-in CSRF protection:
React with Axios
// Set up Axios to include the CSRF token
import axios from 'axios';
// Get the CSRF token from a meta tag
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
// Configure Axios to include the token in all requests
axios.defaults.headers.common['X-CSRF-Token'] = csrfToken;
Django
# Django automatically includes CSRF protection
from django.views.decorators.csrf import csrf_protect
@csrf_protect
def transfer_money(request):
if request.method == 'POST':
# Process the transfer
pass
Testing for CSRF Vulnerabilities
Manual Testing
- Authenticate to the target application
- Identify state-changing operations (transfers, profile updates, etc.)
- Create a simple HTML page that submits a form to the target endpoint
- Test if the operation succeeds without additional verification
Automated Testing
Tools for CSRF detection:
- OWASP ZAP
- Burp Suite
- CSRF Tester
Real-World CSRF Examples
- Gmail (2007): A CSRF vulnerability allowed attackers to change users’ email settings
- ING Direct (2008): A CSRF vulnerability allowed attackers to transfer money from victims’ accounts
- YouTube (2008): A CSRF vulnerability allowed attackers to perform actions on behalf of users
Conclusion
CSRF attacks remain a significant threat to web applications that don’t implement proper protection mechanisms. By understanding how these attacks work and implementing appropriate countermeasures, developers can effectively protect their users from CSRF vulnerabilities.