Using SAML to add abraham.lincoln@whitehouse.gov to my Slack workspace
Note: I use Slack as a funny example in this article, but Slack is not vulnerable to the SAML attacks I describe (they implement option (2) at the end of this post).
Here is me exchanging Slack DMs with Abraham Lincoln:
This screenshot is not doctored. There really is an abraham.licoln@whitehouse.gov in SSOReady’s Slack workspace. They’re a regular Slack user.
How did I do this? There are two possibilities:
- I have, seven score and nineteen years after his untimely death, resuscitated Honest Abe
- His DM to me is a hint. “Don’t believe everything you see in a SAML assertion.”
Sadly for historians, but luckily for B2B SaaS software engineers, the answer is behind door number 2. The trick is to abuse Slack’s SAML integration.
More concretely, here’s what I did:
- Upgraded our Slack to Business+ tier or whatever, so that I could set up a SAML integration
- Pointed said Slack SAML integration at a free instance of Okta
- Created a user with the email abraham.lincoln@whitehouse.gov, full name Lincoln comma Abraham, to my free instance of Okta
- Questioned what I was doing with my youthly years
- Popped open an incognito tab, logged in as Abe in Okta, and then clicked on the Slack tile
- Slack was like “hi Abraham”!
The main point of this blog article is that you can put whatever you like into Okta.
That’s an Okta screen showing you two users:
- Me (ulysse.carion@ssoready.com)
- Abraham Lincoln (abraham.lincoln@whitehouse.gov)
You can just do this. Okta doesn’t verify email addresses. In fact, it usually works the other way around; at many companies, you can’t log into your email unless you can log into Okta first.
Here’s something you should worry about:
- Lots of products have SAML support
- SAML is basically an elaborate “what is your email” protocol
- You can put whatever into Okta, the most popular SAML identity provider
- What happens if I sign up for your product, and then put your CTO’s email address in my Okta, and try to log in as them?
The rest of this article explains that this isn’t some bug in SAML. It’s how SAML is meant to work. And then I’ll explain how software that integrates with SAML – your software – should deal with it.
The “Abraham Lincoln” SAML attack
Let me spell out what I’m talking about before more explicitly.
- I’m a hacker.
- You run some B2B SaaS product. You have SAML support.
- I look up your CEO and CTO’s email address. I look up your biggest and most sensitive customer, and guess the emails of a few of the most important people there likely to have an account in your product.
- I add those email addresses as users in a free instance of Okta I control. Okta lets me do this. Okta lets me log in as them in Okta.
- I set up a SAML connection in your product, and point it at my Okta instance.
- I do a SAML login. Okta sends you a legit, signed SAML request saying I have the victim’s email address.
- I’m now logged in as your CEO / CTO / biggest customer inside your product.
Up until step (6), there is nothing you can possibly do about this. Maybe setting up a SAML connection in step (5) requires business tier, like Slack does. Whatever. That’s hardly a security posture.
Step (7) is where the ball’s in your court. If you roll your own SAML and you didn’t know about this attack, it’s very likely you’re vulnerable. If you have code that looks like this:
def handle_saml_login(request):
email = parse_and_validate_saml_request(request)
log_user_in(email=email)
return
Then you’re vulnerable, because you can’t trust that the email
from a SAML
request is legit. Attackers like me can put whatever email they like in a SAML
assertion, and I’m putting your CEO’s email in there.
The SAML security model
SAML is basically a decentralized OAuth from 2002. It’s a way for an Identity Provider (e.g. Okta) to create messages that a Service Provider (e.g. Slack, your B2B SaaS product) can verify is legit. Your user’s browser can take those messages from Okta and give them to you, and you log the user in. Those messages are called assertions, and they basically just say:
Dear
\[your company\],
Please treat the bearer of this letter as Abraham Lincoln (abraham.lincoln@whitehouse.gov).
Cheers,
okta-instance-1776
| Sent from my iPad |
A real SAML assertion is a big old XML song and dance around messages like this.
Most of the SAML spec is about cryptographically proving that
okta-instance-1776
really did send the SAML assertion.
But nothing in SAML is concerned with proving that the bearer of a SAML assertion really “controls” an email address. I’ll repeat a point I made above again here:
At many big companies, Okta would never verify you control an email address. It’s the other way around. The email server (Gmail) uses Okta to check what inbox to show you.
SAML is decentralized, social login (OAuth) is centralized
OAuth itself is decentralized, but the way everyone uses it is centralized in practice. Nobody has a “Log in with OAuth” button. You have a “Log in with Google” or “Log in with Microsoft” button.
When Google or Microsoft says “this is bob@acme.com
”, you can trust Google or
Microsoft. That trust works because you work with two trustworthy, centralized
OAuth providers.
SAML is inherently decentralized. It’s like if every company set up their own version of “Log in with Google”, and were allowed to make it behave and say whatever they like.
The correct mental model for SAML is the following:
When CompanyA sets up a SAML connection, you should fully trust logins from that SAML connection to the extent it’s related only CompanyA-related things.
Nobody except CompanyA trusts the SAML connection, and SAML connections are untrustworthy by default. The SAML connection should have zero ability to “see” outside of CompanyA’s stuff.
This is at odds with how most B2B software wants to work, because the typical path for most startups is:
- You start simple,
- “Log in with Google” is simple and works well,
- You have a
users
table with aemail varchar not null unique
column, so that when Google says “here’sbob@acme.com
” you know exactly which user they log in as.
And then one day:
- Big customers start asking for SAML
One of two things happens at this point:
- You don’t realize how SAML works, implement it insecurely, and I can do Abraham Lincoln attacks on your product.
- You do realize how SAML works. There are exactly two ways to deal with it.
The only two ways to deal with SAML’s decentralized nature
Both options are basically “scope your trust relationship with each SAML connection to a particular customer”. You can do that trust-scoping at one of two levels:
Note: when I say “user” here, I mean whatever thing in your system humans log into. “Organizations” means whatever thing in your system corresponds to a single company. Sometimes that’s called a “workspace” or a “team” or an “account” or something.
-
The email domain. Internally tag SAML connections with a whitelist of domains. Only honor SAML logins from that connection if they agree with the whitelist.
A lot of products already have a special trust relationship with email domains anyway (e.g. if you log in with Google as
bob@acme.com
butalice@acme.com
is already signed up, Bob gets added as a teammate to Alice). For products like this, domain-based trust-scoping is basically always the way to go.Even if you don’t do this today, it’s typically easy to do so for the big businesses that request SAML. Even if most organizations don’t have a domain whitelist, those that want SAML in your product now do.
Most companies nowadays choose this option. You can always start here, and eventually move to option (2). You usually can’t do the reverse.
-
The session. Tag login sessions with the SAML connection, if any, it came from. Only authorize that session to access resources that are tied to the organization that trusts the SAML connection.
This option is much (much) harder to get right, but it is more flexible if you do right. It usually requires a bit of overhauling of your permissions model. If there are important things tied to users in your system, as opposed to organizations, then you need to move them out, or else come up with some rules to say “you’re only allowed to update your primary email if you’re logged in via email, not via SAML”. Hairy.
AWS works like this. They can do this because “users” are very bare-bones resources in their product, and whenever you log into AWS, you also specify the account ID you’re logging into. Emails aren’t unique between (or even within) AWS accounts. AWS makes almost no effort to have “social” or “marketing” features to users based on email. Most companies don’t work like this.
Let’s replay the Abraham Lincoln attack from before. Okta is sending your product an assertion. The assertion is signed, and proves the following:
- My email is ceo@yourcompany.com
- My Okta instance
evil-okta-123
really sent this message.
If you went with option 1, domain whitelisting, then you reject the assertion.
evil-okta-123
isn’t internally whitelisted to send yourcompany.com
assertions. I can’t control your internal whitelist. My attack is foiled.
If you went with option 2, then I do successfully log in as ceo@yourcompany.com.
But I don’t get anything special. I only get access to my own,
attacker-controlled, organization, because that’s the organization that’s
associated with evil-okta-123
. Not very useful.
Takeaways
I’ll keep it simple.
-
If you’re gonna roll your own SAML, think carefully about whether you’re trusting SAML connections too much. Test whether one customer can “impersonate” another using SAML.
-
Practically speaking, you should implement a domain whitelist to handle this. It’s simple and obvious how it works. Even if today you understand how SAML’s trust model works, are you sure your future colleagues will too? A whitelist is the hardest-to-screw-up option that works here.
-
Consider using SSOReady, either hosted or self-hosted. Domain whitelisting is built-in and required. You can’t get this wrong if you use SSOReady.
Plus it’s MIT-licensed and free.
Godspeed.