I recently received an email alerting me to a security vulnerability in Bloopist, my custom blogging platform. The security vulnerability would have allowed attackers to impersonate other users on Bloopist after getting them to visit a web page containing malicious JavaScript. I have no proof that this was done, and the security vulnerability is now closed. The root cause was a CORS misconfiguration.
Want to learn more about CORS? Check out CORS in Action: Creating and consuming cross-origin APIs.
Thanks to Jens Mueller, I was alerted to this issue. He was kind enough to send me the email below:
*Please forward to tech/developer/security team* TL;DR +++ bloopist.com generates the Access-Control-Allow-Origin header in a dangerous way +++ websites like bloopist.com.evil.com are allowed CORS access +++ leads to SOP bypass (aka completely taking over accounts) and SSL bypass +++ Fix: disable CORS dynamic header generation or re-config to trusted URLs +++ - Affected service: bloopist.com - OWASP Top 10 category: A5 (Security Misconfiguration) - Impact: Take over user accounts, SSL/TLS bypass Dear bloopist.com security team, In the scope of academic research on web security, we touched upon a vulnerability in bloopist.com. The website uses Cross-Origin Resource Sharing (CORS) in an insecure way: *** Weakness: Post-domain wildcard origin reflection *** bloopist.com's *Access-Control-Allow-Origin* header is dynamically generated based on the browser's *Origin: ...* request header. The generation code however only checks if the Origin *starts* with bloopist.com. Furthermore, the *Access-Control-Allow-Credentials* header is present. This allows an attacker to bypass access controls such as the same-origin policy. For example, a malicious website like bloopist.com.evil.com visited by a client logged into bloopist.com can perform actions in the context of the logged in user on bloopist.com. Furthermore, this allows a MitM attacker to bypass SSL encryption. *** Exploit: SSL-bypass (scenario: MitM attacker) *** /A http origin is allowed CORS access to a https resource,/ /this allows a man-in-the-middle to break https encryption/ https://bloopist.com allows CORS-access from non-encrypted origins like "Origin: http://bloopist.com". This enables a man-in-the-middle to practically bypass SSL encryption: The attacker just has to wait until the victim visits *any* unencrypted website, insert a redirect to a fake http://bloopist.com.whatever site she set up -- and then embed JavaScript code here, which fetches the user's data from https://bloopist.com. Now -- via CORS -- she can access the SSL encrypted content of https://bloopist.com/some-private-user-info or perform arbitrary actions in the context of the logged in user. Of course, a MitM is a strong attacker model. However, it's what SSL actually was supposed to protect from. More details on CORS-misconfiguration issues can be found here: https://web-in-security.blogspot.de/2017/07/cors-misconfigurations -on-large-scale.html http://blog.portswigger.net/2016/10/exploiting-cors-misconfigurati ons-for.html https://www.youtube.com/watch?v=wgkj4ZgxI4c Proof-of-concept code can be sent on request. Feel free to contact me for any questions. Greetings, Jens Mueller -- Chair for Network and Data Security, Ruhr-University M.Sc. Jens Mueller Universitaetsstr. 150 Building ID 2/469 D-44780 Bochum Phone: +49 (0) 234 / 32-29177
Basically, the regular expressions I was using to allow CORS access were broken. I needed to modify them to only allow https access, and ensure that only domains that ended in bloopist.com should be allowed.
Testing for CORS Vulnerabilities
To test which origins were allowed by my CORS configuration, I used a simple curl command:
curl -H "Origin: https://bloopist.com.evil.com" --verbose https://bloopist.com 2>&1 | grep Origin
This command let me pick the origin that I wanted to pretend to be and printed out any header lines that included the word "Origin". From that, I could see if the server was sending back an Access-Control-Allow-Origin string that would allow attacks from other domains or not.
Before implementing my fixed CORS whitelist, I'd get outputs from curl like the one below.
$ curl -H "Origin: https://bloopist.com.evil.com" --verbose https://bloopist.com 2>&1 | grep Origin
> Origin: https://bloopist.com.evil.com
< Access-Control-Allow-Origin: https://bloopist.com.evil.com
< Vary: Origin
Protecting CORS Access
I updated my CORS regular expression to force https, allow either a subdomain of bloopist.com or the bare domain by itself, and to force the origin to end with bloopist.com. I use Rack::Cors in my Ruby on Rails applications, and I configured it like this:
# Access-Control-Allow-Origin
config.middleware.insert_before 0, Rack::Cors do
allow do
if Rails.env == "development"
origins('*')
elsif Rails.env == "production"
origins(/\Ahttps:\/\/(.*?\.|)bloopist\.com\z/)
end
resource '*', :headers => :any, :methods => :any
end
end
Results
After updating my configuration, I double checked which origins were allowed CORS access. Domains other than bloopist.com are no longer allowed access. However, I was unable to figure out how to make curl indicate that it was coming from the null origin, so I was unable to test that that particular vulnerability was closed.
Attacker Origin | Allowed Before? | Allowed After? |
---|---|---|
https://bloopist.com | Yes | Yes |
https://blog.bloopist.com | Yes | Yes |
https://bloopist.com.evil.com | Yes | No |
http://bloopist.com | Yes | No |
null | ? | ? |
Do you know how to get curl to use the null origin? Did you find this information helpful? Let me know in the comments.