Hacker News new | ask | show | jobs
by jussij 969 days ago
Can someone with OAuth expertise explain this issue in a few more details, as I've read the blog a few times, but still don't understand the actual vulnerability.

From my very limited OAuth knowledge isn't this how it works:

1. The Harvest application asks Microsoft to verify a user. 2. The user is verified by Microsoft. 3. If the user verification is successful Microsoft redirects back to the callback URL, passing back the access token inside the body of the response message.

In this case hasn't the writer of the blog just created a hand-crafted URL so that the return is back to example.com rather than the actual return URL?

4 comments

Yes, the blog author has created such a hand-crafted URL, but note that the callback url in the attack's url is to a harvestapp domain, and the attacker controlled portion is in the state, which is pretty much opaque to the oauth server.

That url allows you to link someone to a login.microsoftonline.com link, have a login prompt show up that says "login to harvestapp", and then have the attacker be able to gain permissions related to your real harvestapp account.

Normally, this would not be possible. The attacker with example.com could register a new app that does redirect to example.com, but that would not give them an access token with permissions related to harvestapp, so it would not be useful.

The oauth app, on microsoft's end, has a whitelist of valid redirects, so an attempt to do something like "login.microsoftonline.com/authorize?client_id=$harvestAppID&redirect_uri=attacker.com" will error out on microsoft's side, since that is not a valid redirect uri to receive an access token.

The attack is only possible because there's a valid "outlook-integration.harvestapp.com" URL, which receives the access token, but then also redirects to the attacker's site and gives them the access token too.

The vulnerability came from the outlook-integration.harvestapp.com. It used a JSON object as `state` containing instructions once the OAuth2 Callback succeeded.

The property `subdomain` was used to redirect the browser to a subdomain of harvestapp.com, passing the `#id-token`. The problem came from the fact that the value of `subdomain` was injected directly to:

https://${subdomain}.harvestapp.com/...#id-token=...

By setting the `subdomain` in JSON payload to `attacker-controlled.com/` (note the trailing slash), the URL become:

  https://attacker-controlled.com/.harvestapp.com/...#id-token=..
..thus redirects the browser to another domain, leaking the token.
So it was the combination of:

* the additional redirect using the JSON object in state * the `subdomain` not being properly verified * the implicit grant being supported

Which allowed an attacker to get an access token for a user's Microsoft account.

From my reading, this seems to be entirely an issue due to an improper implementation on Harvest's side, nothing to do with Microsoft's implementation of OAuth. Am I correct?

Clearly not, or I doubt we would be reading this blog post.

I assume that for several years though, that was exactly what Microsoft thought too.

What am I missing then?

It seems pretty clear to me from reading the blog post that the issue was what I outlined (sorry for the lack of list formatting, I always forget I need an extra line after each bullet point).

Not sure what parent was talking about. You are correct. This is Harvest’s responsibility, not Microsoft’s.
I didn't understand the article correctly. I was wrong.
Good explanation. Quick follow up, so to resolve this issue, what I have in mind are :

1. Make sure the redirect url is a valid harvestapp.com (more checks on state)

2. Encrypt the state since the start of the request, so then they can double check the state hasn't been forged by decrypt and compare

Is there any option beside those?

All they had to do was sanitize the subdomain var to only allow values valid in host part of a URL. But also, one of the state parameter's primary uses is exactly to prevent XSRF attacks like this by using a random nonce value so that you can validate from the redirect that your system was the initiator of the auth request. The data in this state was not sensitive, so encryption is not really necessary.
Why not just use a random ID and pull from DB instead of shuffling around a json payload? Really trying to avoid that DB hit? Just pay the price imo
Microsoft checks the return URL to see if it is one of the whitelisted URLs specified by Harvest. Harvest added their own redirection mechanism on top of this, presumably to support multiple instances of their software, which did not do a good job of sanitizing input values for their redirect. So no, this is not an implicit issue with oauth, just a shoddy implementation.
Ok, I think I understand but correct me if I'm wrong. Normally that return URL would be hidden from view, as it would live in configuration detail found inside of the Microsoft system, attached to the client_id. However, Harvest weakened this security by adding in the additional (and unsafe) return_to parameter to manage their return URL.
That's the gist.

> The authorization server SHOULD require the client to provide the complete redirection URI (the client MAY use the "state" request parameter to achieve per-request customization). If requiring the registration of the complete redirection URI is not possible, the authorization server SHOULD require the registration of the URI scheme, authority, and path (allowing the client to dynamically vary only the query component of the redirection URI when requesting authorization).

> The authorization server MAY allow the client to register multiple redirection endpoints.

https://datatracker.ietf.org/doc/html/rfc6749#section-3.1.2....

Either the redirect URL is statically configured, or it's accepted as a query param to the auth request, and subject to a strict whitelist. It's not a secret from the user, but even for a SPA it is usually transient so you don't have the user sitting at some ugly URL with "?code=abc123...". Typically you would use the state query param to retain any context needed to redirect the user to their desired destination, but that would be after the redirect endpoint uses the passed code to fetch the token and store it somewhere locally. In this case apparently the redirect endpoint allowed redirecting to entirely different applications by simply forwarding on the sensitive query params, but did not validate that those destinations were on any whitelist.

Not as far as I recall (haven’t done OAuth in a hot minute) but the redirect URL is typically in the GET parameters or in the body of the request, neither of which is hidden from view.

This issue seems to be that there was a secondary redirect in the body of one of the requests (I believe the token response), that could be forged to loosely match a trusted domain but with an attacker’s domain present, eg “//attacker.com/trusted.com/“.

hi ^^, limited knowledge as well, however I'm pretty sure the issue is that Harvest allows all urls to be used as callback urls. You should tell microsoft to allow only certain urls as callbacks. eg, when setting up the workflow, they probably used a wildcard as an allow list of callback urls, instead of creating an actual list of trusted callback urls. I think that's what's happening here, could be totally wrong tho :D