Okta's Not So Secret, Less Secure, API Authentication Method
Every week, almost without fail, I come across one thing that confuses, entertains, or most commonly infuriates me. I’ve decided to keep a record of my adventures.
Occasionally, when surrounded by the right folks (and right drinks) we'll collectively travel down the rabbit hole of 'the biggest issues with security programs'. Undoubtedly, one of my top answer is organizations' 'failure to play with their toys'. By this I mean that teams buy vendor tools, but really don't use them to their full potential.
APIs (Author's Note: which should always be freely included) allow customers to build the missing integrations that vendors haven't gotten to (or won't get to) themselves. As a result, I work with a lot of APIs and have pretty strong opinions about their design. Today, we're gonna talk about Okta's API and specifically some (arguably) bad authentication decisions the... authentication company made.
Background
Most APIs are pretty straightforward. This is by design; APIs represent well-specified interfaces to data and actions within a platform. In support of this, most modern APIs are structured in a RESTy manner. Authentication to APIs is most commonly done via API tokens or OAuth. OAuth (in many flavors) has the benefit of supporting things like MFA as part of the authentication process and has therefore become the more favored approach, however API tokens are still ubiquitous.
What is less common is cookie-based authentication. At the risk of rekindling existing philosophical debates, cookies are certainly the least REST-compatible authentication mechanism. I won't delve too far into the specific reasons I believe this, but, I'll summarize my argument as: It's difficult to reasonably separate cookies from state, which makes them inherently less 'stateless'.
The Problem
Okta's API has a strange, somewhat abusive, relationship with cookies. The most obvious example of this is that all Okta's read APIs can be called with a valid session cookie, directly from the browser. In fact, ALL Okta's APIs I tested could be called with cookie based authentication, but there's a catch, as we'll see. You'd be forgiven for not knowing this, as it's not documented and before you even ask, there is no way to disable or limit this. Don't believe me? Try accessing https://{yourtenant}.okta.com/api/v1/users.
When used, session cookies act like API tokens in that APIs accessed with cookies don't require MFA, device trust, or posture checking, even if the admin UI requires that. This is perhaps the number one reason that this capability shouldn't exist. For the average (or even above average) Okta admin, it's not straight forward to understand the ramification of cookie/session theft. Said another way, its tempting to think that the Authentication Policy that exists for the Okta Admin UI is a security boundary, when in fact its not. I'm going to talk a little bit more about this, but first, let me elaborate on the problem.
Recall above I split this issue into 'read API calls (GET requests) from a browser' and 'all other API calls (POST, DELETE, PUT, etc.)'. The reason I did that is that Okta relies on the Same Origin Policy (SOP) and by extension Cross Origin Resource Sharing (CORS) to gate access to POST, DELETE, and PUT methods.
The astute reader, who is also overly familiar with CORS, would be a bit confused here. Their concern is justified. Standard CORS restricts communication by leveraging 'preflight requests' in combination with headers to determine if a subsequent cross origin request should be sent. Not a very robust system for API restrictions, right?
There are two types of CORS request, 'simple requests' (this includes basic GETs and POSTs), which don't require preflights, and 'advanced requests', which do. Now, you might be tempted to say that all Okta APIs require application/json content-type headers, which makes them 'advanced requests', sorta true (turns out that's not always the case - but more in a bit). So, given all this, the roundabout question is, how is Okta's API preventing a browser making a POST request from say an HTML form, which is not beholden to CORS? Said another way, how is this not vulnerable to Cross Site Request Forgery (CSRF)?
The answer is: the way they utilize the 'origin' header. This is actual somewhat clever (and they're not the first to think of this). Because browsers won't let Javascript, forms, or any other cross-origin request control the 'origin' header. If you, being Okta, only allow requests with an 'Origin' header value from "trusted-domains" (which includes {tenant}.okta.com), then you are "guaranteed" that any attacker prompted request made in a victim's browser won't be successful. Right?
While that's an interesting question, the real question here is, "is this an effective protection against CSRF?" Yes, but there are a couple of gotcha situations to using CORS/SOP in this manner that make it less than ideal.
领英推荐
I suppose its worth demonstrating that this is true. Here is an example where a session cookie is used to call the user delete API. Again, every API tested works with this authentication method.
While testing this, I discovered a couple interesting things. For starters, POST requests that require content-type's of application/json but don't actually take parameters are more than happy to respond to application/x-www-form-urlencode. That could be handy if you can only inject HTML into a compromised trusted origin.
Additionally, not every API requires application/json as a content-type, regardless of what the docs say, some APIs accept multipart forms. These still required an Origin header to use in testing, which is good, but seems like an interesting anti-pattern. They are:
So on the bright side, it appears that the enforcement of the origin requirement is consistent. On the down side, this opens up the possibility of error by introducing a GET based API that undertakes an action or has a side effect. I think its pretty unlikely that any of these would appear in the user facing APIs, however the undocumented APIs are a different, and unexplored, story.
The Solution
There are three general issues with Okta's approach here:
To be fair to Okta, there are common sense actions admins should take to prevent such issues.
Fun right?
Thanks! Two items; 1) Global Session Policy = refer to apps = adhere to the Admin app policy. What you refer to is compability mode/legay policy setting, right? 2) Two accounts for privileged users, one user account for normal usage and one for admin will reduce the likelihood of user account compromise lead to this.
Google Cloud Certified Digital Leader | Azure Security Engineer Associate | Microsoft Security Operation Analyst Associate | (ISC)2 Certified In Cybersecurity | CompTIA Sec+
1 年Any comment from Okta ?