SAML and SAML Attacks
Recently a client mentioned that they wanted me to pay particular attention to the SAML authentication on an app I was going to be testing. It’s been a while since I’ve done anything with SAML, so I thought I’d refresh myself on SAML basics and SAML attacks.
SAML stands for Security Assertion Markup Language. It’s an XML-based standard for exchanging authentication and authorization data between identity providers (IdP) like Okta, Google, Microsoft Azure AD, and more; and service providers (SP) including big services like Box and Google, as well as countless custom applications. SAML is one technology enabling single sign-on (SSO), allowing a user to authenticate to multiple services with credentials managed by a single IdP.
When a user goes to log in to a service using SAML for authentication, here’s what happens.
- The user’s browser requests access to the SP
- The service provider generates a SAML request for the IdP, asking if the user has permission to access that resource
- The SP sends the SAML request to the user’s browser and redirects it to the IdP
- The IdP parses the SAML request and authenticates the user
- The IdP generates a SAML response
- The IdP sends the SAML response to the browser and redirects it back to the SP
- The SP validates the SAML response
- The SP logs the user in
A SAML request is an xml document specifying what SP the user is requesting access to.
A SAML response is an xml document carrying information about the sending IdP and when the response was generated, as well as an Assertion indicating that the user was successfully authenticated. The Assertion may carry additional information about the user such as their phone number, address, nickname or other details in the form of Attributes. It may also carry information about Authorizations, which describe a user’s access rights to different resources.
Either the assertion, the entire SAML response, or both may be cryptographically signed to ensure their validity.
Con Badges as SAML SSO
One way to think about SAML and SSO is like going to a conference. At a con, before you access any of the events (aka service providers) such as speakers, villages, CTFs and vendor areas, you’ll encounter staff checking for your badge. If you don’t have a badge, you get redirected to the registration desk. The registration desk (aka the identity provider) verifies your identity and authenticates you. If you’re registered for the con, you get a badge, which acts like a SAML response. At many cons, the badge’s color and shape serves as a SAML authorization, detailing what you have access to: attendee areas like villages and speaker tracks, the prep room for speakers, or all the backstage areas for staff. Badge in hand, you can now go and enjoy the con, without having to re-register or re-authenticate at every door.
SAML implementations can be vulnerable to a variety of attacks. Some implementations may not properly validate signatures. Others may not check that a SAML response was actually intended for the SP receiving it or may accept additional SAML responses tacked on to the real one. And because it’s XML, XML External Entity (XXE) and Extensible Stylesheet Language Transformation (XSLT) attacks are also possible.
Below I’ll demonstrate three attacks: signature removal, signature replacement and condition violation.
To help practice these attacks. I built a small practice environment. It’s based on the example IdP and SP in Tim Heap’s flask-saml2 repo, which I’ve modified to include some additional vulnerabilities. SAML-practice spins up three docker containers, hosting an IdP and two SPs, on ports 8000, 9000 and 9001, respectively. Running the start-saml-practice.sh script will prompt you for your host’s IP address, configure and build the docker containers accordingly, then run them. For most of the exercises below, I demonstrate attacks using the SP running on port 9000 (Sp0), but either SP will work. Only the third attack requires the use of both SPs at once.
In my case, I configured my containers to run on my host ip of
http://192.168.109.175:9000/‘ I receive the message that I’m logged out, and that I can log in to continue.
Clicking the link, I’m redirected to a login page at
http://192.168.109.175:8000/login/ — the IdP — and prompted to select a user.
After I select a user and hit ‘login’, I very quickly see a redirect page flash up, before I’m redirected back to the SP, which informs me I’m logged in and the IdP sent back the attributes ‘foo’: ‘bar’. There are no authorization fields in this assertion).
Now I open up Burp and repeat the process using its built-in browser. Looking at Burp Proxy’s HTTP History I can see the authentication flow play out. Reading from the bottom up is my initial request to the SP (line 25), the redirects to the IdP (26-29), the POST where I identify myself (line 31), and then the redirect back to the SP (33), followed by the SP confirming I’m logged in.
The SAML response, which is what interests me, is in line 33, the POST request to the SP
http://192.168.109.175:9000/. The actual SAML response is the large base64-encoded parameter at the bottom labelled, clearly enough “SAMLResponse”.
Activating the SAML Raider extension, I can examine the response’s XML, seeing that it has a signature block at the top, and a single assertion, giving our selected user access to the system. The question now is, can I leverage this to gain access to someone else’s data?
The simplest attack is to remove all the certificates and signatures from the SAML response. Some SAML libraries will rigorously validate every signature they’re given. And if they’re given no signature, they’ll look, see that no signatures have failed to validate, and let you in. In this case, I’m going to search for NameID field where the Response identifies me as ‘firstname.lastname@example.org’ and change it to ‘email@example.com’, then remove the signatures. With no signatures, I don’t need to worry about the data tampering causing a signature mismatch. First I change the names.
I then hit the “remove signatures” button.
Turn interception off, and…
In a secure SAML implementation, the IdP and SP should exchange certificates out-of-band before the application goes into production, so they can verify that the certificates being used to sign messages are the correct ones. Some service providers will insecurely verify a SAML Response’s signatures against the certificate provided in the response. In those cases, tampering with a message, then re-signing it will allow it to pass muster.
To test this, I again go the the login page at
http://192.168.109.175:9000/. Again, I turn on interceptor, select ‘firstname.lastname@example.org’ and then forward requests until SAML Raider activates.
As before, I’m going to replace alex with jordan. But before I remove the signatures, I’m first going to hit the “Send Certificate to SAML Raider” button.
That sends the certificate to the SAML Raider Certificates tab, which allows you to edit, and more importantly, re-sign certificates so you can use them to generate signatures. So I hit the “Save and Self-Sign”, then go back to the Interceptor tab. At this stage I could also generate my own X.509 certificate with OpenSSL, import it into SAML Raider and use it the same way as I’m about to use the re-signed certificate.
Now, the certificate drop-down that was empty is filled with the certificate I re-signed. I remove the existing signatures, change the user from Alex to Harry, and then re-sign the SAML Response with my tampered certificate. Re-signing the SAML Response has to be the last step, as any changes made after that point will invalidate the signature.
I turn off interception, let the authentication flow finish, and I’m logged in as Harry.
SAML responses can have one or more conditions limiting their use. Typical ones include time limits on the response’s validity (e.g. NotBefore and NotOnOrAfter), and audience restrictions. If a service provider does not check these conditions, it becomes possible to do things like save responses for reuse later, or capture a response generated for one service provider, and send it to a second. The screenshot below shows the conditions in one of the SAML Responses I’ve been working with in this blog post, including time limits and audience restrictions.
The saml-practice repository includes a second service provider, running on
http://192.168.109.175:9001/sp1 which ./start-saml-practice.sh starts up alongside the first SP and the IdP. For this exercise I open an incognito window (using a second browser would work, too) to interact with the other SP, to prevent cookies issues causing unexpected side effects. With this second service provider, I can practice Audience Substitution – using a SAML Response generated for one SP with a different SP.
I start by logging in to the SP on port 9001 (Sp1). I don’t even need to use Interceptor; I can just select the user I want and let the authentication flow run. In this case, I log in as Harry. In the HTTP History window, I find the POST request to
http://192.168.109.175:9001/sp1/saml/acs, which contains the SAML Response from the IdP to Sp1. I need the base64 encoded version, not the XML for this, so I’m viewing the request in Raw mode. I copy the entire encoded SAMLResponse parameter.
In a second browser, or an incognito window, I go to
http://192.168.109.175:9000/, which will start the login flow for Sp0. This time, I’ll use Interceptor, as I did in the other two attacks, starting my login as Alex or Jordan. When the SAML Response shows up in Interceptor, change the mode back to Raw, and replace the SAMLResponse parameter in the request with the one I copied out of the request for Sp1. I turn interception off and when the authentication flow finishes, I’m logged in to Sp0 as Harry.
There are a whole host of other attacks involving XML signature wrapping, which I wasn’t able to build into this practice environment, but this should be enough to get you started with testing for SAML vulnerabilities. If you’d like to experiment a bit more, there are a couple of hidden bits in the practice environment that take advantage of the attacks described above. Play around a bit and see if you can find them!
Hint: Try using names that are available in one SP, but not the other, or maybe common usernames that would give yo u more access in a real environment.
Related StoriesView More
Bypassing Signature-Based AV
By Red Siege | August 25, 2021
If you want to execute arbitrary code on an endpoint during a penetration test, red team, or assumed breach, chances are you’ll have to evade some kind of antivirus solution. […]Learn More
Sans Core Netwars Tournament of Champions Europe
By Red Siege | August 9, 2021
From Justin Palk, Security Consultant: I’ll be honest, it feels good to win. Popping a shell sends a shiver down my spine. But getting into a duel with another team […]Learn More
By Red Siege | March 22, 2021
The intent of this blog is to help penetration testers and security researchers get a deeper understanding of the OAuth protocol. We are going to learn how to bypass authentication […]Learn More