Exploit a misconfigured CORS - Lab
TL;DR - show me the fun part❗
-
Victim login into vulnerable app.
-
Victim visits attacker page.
-
Attacker gets leaked API key🎉.
I don’t get it. Show me the flow 🔎
-
Authenticated user visits attacker website.
-
Attacker’s website contains a malicious JS which asks the browser to send authenticated cross-origin request (via XMLHttpRequest or fetch) to the vulnerable endpoint at example.com.
-
The vulnerable endpoint mistakenly reflects the request’s Origin header into
Access-Control-Allow-Origin
response header. -
The browser sees the attacker’s origin is allowed. Therefore it allows the JS to read the response.
How did this happen❓
We talked before that CORS is a way to make an exception to same origin policy. It allows Javascript (JS) to READ a cross-origin resource.
In JS you can make authenticated corss-origin requests. Meaning, a JS can tell the browser to submit the cookies automatically along side with the request.
To allow this kind of authenticated requests, the destination endpoint must explicitly allow authenticated requests via HTTP header
Access-Control-Allow-Credentials: true
When this header 👆 is used, then also the endpoint must explicitly specify
an origin in the value of the
Access-Control-Allow-Origin
header, for example
Access-Control-Allow-Origin: https://origin.secure-cookie.io
Since developers must specify an origin. Developers mistakenly allow ANY origin via reading the origin of the incoming request and then “reflect” it in the response header:-
Access-Control-Allow-Origin: <REQUEST ORIGIN HERE>
This cause a security flaw because the request can come from attacker’s origin, and it will be reflected in the allowed header. Thus the JS from the attacker’s website will be allowed to READ the response and leak the data of the user.
Analysing the attack 👀
Let’s have a second look on the vulnerable application
-
Start network monitor in your browser developer tool (I will be using Firefox).
-
Login into
https://csrf.secure-cookie.io/login
. -
An authenticated user can generate API key using “Generate API key” button.
-
Clicking on this button, will trigger this JS snippet
function get_api_key(){
var options = {
method:"GET",
credentials: 'include'
}
var users = fetch(url="https://demo-api.secure-cookie.io/getKey",options);
users.then(resp => {
return resp.json()
}).then(data => {
update_key_html(data);
})
}
-
The script will ask the browser to make GET request to
https://demo-api.secure-cookie.io/getKey
. -
credentials:'include'
option, will tell the browser to make authenticated request (add cookies along side the request). By default without this option, a browser will not include the cookies. -
The vulnerable endpoint response header is:-
Access-Control-Allow-Origin: https://csrf.secure-cookie.io
Access-Control-Request-Method: GET
Access-Control-Allow-Credentials: true
- Reflects the request’s origin (
https://csrf.secure-cookie.io
) in “Access-Control-Allow-Origin” header. - Allows GET method.
- Allows the caller to send authenticated request(with cookies).
How an attacker can exploit this❓
Let’s find out! In a new tab, open the attacker page.
-
Notice that as soon as you (victim) open the page, the API key gets hacked
-
The attacker’s page contains a JS snippet that does the same exact job that we explained in steps (4, 5 ,6) above.
-
The vulnerable endpoint “reflects” the attacker origin “
https://s3-eu-west-1.amazonaws.com
” in the response header as you can see:-Access-Control-Allow-Origin: https://s3-eu-west-1.amazonaws.com Access-Control-Request-Method: GET Access-Control-Allow-Credentials: true
-
Since the attacker origin is allowed, the JS in the attacker page can read the API key 🎉.
Show me the source code of the backend❓
For this demo, I used Python as backend. The source code of the vulnerable endpoint /getKey :-
def get_key():
rcvd_origin = request.headers.get("Origin")
resp = make_response(jsonify(key=GetRandom.session(size=16)))
resp.headers['Access-Control-Allow-Origin'] = str(rcvd_origin)
resp.headers['Access-Control-Allow-Credentials'] = 'true'
resp.headers['Access-Control-Request-Method'] = 'GET'
return resp
- Read the incoming request header, get the value of the Origin.
- Reflect the incoming origin in the ‘Access-Control-Allow-Origin’ response header.
- Send the response.