Security Assertion Markup Language 2.0, or more commonly known as SAML in the industry, is one of the most used protocols for single-sign-on on the modern web. It allows an application like Teleport to communicate with an upstream identity provider like Okta or Google Workspace to securely get trusted information about users when they log in, removing the need for sign-ups, log-ins and tying identities to people inside the application.
Today, SAML and other forms of SSO negotiation are the de-facto standard for handling authentication within organizations. Currently, SAML maintains a lead in flexibility and feature-support for enterprise usage. Notably, SAML supports a one-click login procedure known as IdP-initiated login that allows users to authenticate to a variety of apps in one place with a single click; in this article, we explore implementing this method of authentication as a relying party and discuss its effects on application security.
SAML is quite heavy on terminology to describe the respective parties in a flow and to explain various concepts. The rest of the article will use these terms:
- Assertion: A set of claims/information about a user in the form of key-value pairs. These contain information like email and roles. These are signed by the IdP so that the SP can verify their authenticity.
- IdP: Identity provider, this is the service handling authentication that issues SAML assertions. This is usually a third party like Azure AD or Google Workspace, but can be any system, public or internal, that implements the SAML identity provider protocol.
- SP: Service provider, this is an app which consumes SAML assertions containing identity information. This information is often used to create permanent or temporary accounts mimicking local users on the platform, deferring authentication duties to the IdP. For example, Teleport can act as the SP when configured to use a SAML connector.
With the goal of being applicable to a wide range of real-world scenarios wherein a user starts and ends at different places, SAML supports multiple ways to start and end an authentication process. In the world of SSO, these are commonly referred to as flows.
Ignoring specialized cases, the most common flows in use today are the SP-initiated and IdP-initiated login flows. Below, we describe the steps taken during each flow at a high level.
In the SP-initiated flow, the authentication is always started by the user navigating to the SP.
- The user navigates to the SP and clicks a login button.
- The SP redirects the user to the IdP with a SAML request.
- The user authenticates at the IdP.
- The IdP redirects the user back to the SP with a SAML response containing an assertion.
In the IdP-initiated flow, the authentication is always started by the IdP, usually in response to the user selecting the SP from a list of apps.
- User signs into IdP dashboard
- User selects the SP from a list of apps in a dashboard
- The user is already authenticated at the IdP and is thus automatically redirected to the SP with a SAML response containing an assertion.
SP-initiated vs. IdP-initiated
For a long time, SP-initiated has been the most commonly used authentication flow and still is for numerous apps; however, lately, the IdP-initiated authentication flow has gained ground quickly as identity providers began offering easy-to-use app dashboards. These dashboards show a logged-in user all the apps they can access in a single place and allows the user to access the app logged in with just one click. Convenient!
Previously, Teleport has only supported the SP-initiated flow, but we’re changing that with Teleport 10.1.9 by introducing native support for IdP-initiated login to our web interface. Consequently, we’ve had to consider the possible attack vectors this change opens up and how we should mitigate them in Teleport.
SAML, like most complex systems, especially ones dealing with sensitive procedures like authentication, has some common attack vectors that can be exploited if the implementation isn’t properly secured. Notably, the IdP-initiated SAML flow is more difficult to secure than its SP-initiated counterpart.
If we take a look at how an SP-initiated flow is commonly implemented at the service provider, we can see that before the user is redirected to the IdP for authentication, we save the authentication request along with a randomly generated token for the request to some persistent store. At the same time, the generated token is embedded in the custom
RelayState SAML request parameter, which allows passage of an opaque string value throughout the authentication flow. Once authentication is completed and the IdP redirects the user back to the SP, this parameter from the original request will be embedded in the assertion response signed by the SP. Once we decode the assertion and verify its integrity, we can check the received token against the one we recorded earlier and ensure they match. This allows the application to associate every response with a request, ensuring that every completed login flow was intentionally started by the user.
In a flow where the service provider doesn’t control the initiation such as IdP-initiated login, the application does not have the opportunity to record the login request by itself. After combing through the [SAML specification], we discovered three potential attacks that originate from this problem. Special care should be taken when implementing these areas in an SP integration.
In an SP-initiated flow, the SP records the
RelayState parameter of every assertion we receive and checks it against the previously stored request. But in an IdP-initiated flow, we can never provide this value through a request, so we never get it back to perform any verification. Since the SP is not already expecting logins initiated by the IdP, an attacker can replay a successful login that has already occurred and trick the SP into authenticating the user.
SAML mandates that assertions contain
not-after timestamps, which limit the effective duration an attacker has to reuse the token. This makes the job of the attacker a lot harder, but many IdP’s still configure this time window to be significant, often on the scale of minutes, which is more than enough time to mount an attack. The SAML standard luckily mandates a loosely specified variable called the session index, which is stored per assertion in the response payload.
The contents of the session index value are arbitrary, and are not guaranteed to be globally unique on an infinite timescale. However, based on how most IdPs implement this value, we can trust that it is at least unique for as long as the assertion is valid, which is identical to the time range specified by the
not-after values. As a result of this, we can effectively mitigate this attack by storing a timed entry in a database with the session index of all assertions we record, expiring at the timestamp specified by
As described above, SP’s may use
RelayState or another parameter such as
RequestID to pass an arbitrary token along. To prevent an assertion intended for a IdP-initiated flow being used for an SP-initiated flow or vice versa, the SP should differentiate between the different flows using the existence of this parameter. Even if we have a different callback URI or other parameters we use to distinguish between the flows in a SAML assertion, we should still validate that all assertions the SP receives are either IdP-initiated and should be treated with caution, or alternatively match a corresponding request stored as part of an SP-initiated flow.
Interception attacks are a special kind of degenerate attack in where an attacker somehow captures and uses an assertion from an ongoing login flow before the user's browser can complete it and log in; which may allow an attacker to gain a "ghost" session which the user doesn't know about. This problem isn't unique to the IdP-initiated flow, but worth mentioning, as verifying assertions becomes harder in this case. This attack cannot be conducted on its own, but could form an essential part in a larger exploit chain that allows exfiltration of the assertion in the first place.
This type of attack is already made more difficult by the mitigation employed for replay attacks, this means that an attacker must race to use the assertion to gain access faster than the original user, since it becomes invalid after SP sees it for the first time. Furthermore, this risk should be reduced by shortening the "trust window" i.e., the period of time the assertion is valid within. Since this is controlled by the IdP, there isn't much that can be done on the SP-side except encourage a defensive configuration in the relevant documentation. By doing this, even if the attacker successfully disrupts the login process of the original user, they have a very restricted time window to complete the attack before the assertion goes stale and the SP can warn that something is wrong.
After discussing the above attack vectors, we can conclude that although the IdP-initiated SAML flow can be secured by employing the mitigations above, it’s not without risk. Thus, IdP-initiated login may not be a viable trade-off for all applications, and a security evaluation should be conducted before enabling the feature. For this reason, Teleport ships with IdP-initiated logins disabled by default and requires explicit opt-in by a cluster administrator.
SAML and especially its IdP-initiated authentication flow comes with a set of challenging security problems which require careful attention to properly harden during implementation of a service provider. In this post, we looked at a common set of attack vectors that open up when supporting SAML’s IdP-initiated authentication flow and how Teleport is hardened to minimize the chance of exploitation.
SAML and SSO tech in general are still evolving and there are still difficulties to address, some of which we’ve touched upon in this blog post, but we can drastically improve the practical security of an application with these mitigations. To stay up to date with Teleport's latest developments, keep an eye on our GitHub page.
- Auth0: Configure SAML Identity Provider-Initiated Single Sign-On
- OWASP: Unsolicited Response (i.e., IdP Initiated SSO) Considerations for Service Providers
- How SAML 2.0 Authentication Works?
TLS Routing Support for Teleport Behind an AWS Application Load Balancer
By Steve Huang
What’s New in Teleport 11
By Kenneth DuMez
A Simple Overview of Authentication Methods for Kubernetes Clusters
By Tiago Silva