Bypassing SAML 2.0 SSO with XML Signature Attacks
Table of Contents
We’ve recently noticed a trend with a lot of New Zealand sites wanting to implement Single Sign-On (SSO) to combat the proliferation of passwords, including many government services. The most prevalent standard for doing this, providing interoperability between many vendors’ frameworks and multiple languages, is SAML 2.0. The usual mechanism for this passes the SAML response certifying the user’s identity through the web browser, using a signature to prevent tampering. Unfortunately, many SAML consumers don’t validate responses properly, allowing attacks up to and including full authentication bypass.
SAML 2.0 SSO #
When signing in to a site with SAML 2.0, there are three parties involved - the Service Provider (‘SP’, the web application we want to access), the Principal (the user logging in) and the Identity Provider (‘IdP’, the authority). We want to accomplish the aim of getting the Identity Provider to tell the Service Provider, in a trustworthy way, who the Principal is.
We do this by having the Service Provider redirect our user to the Identity Provider with a SAML request. Once the Identity Provider is satisfied as to the user’s identity, they send them back to the Service Provider with a SAML response. There are three major ways of sending a message for web SSO, which the standard refers to as “bindings”:
- HTTP Redirect Binding. We include the SAML message directly in a URL.
- HTTP POST Binding. We include the SAML message in the body of a POST request.
- HTTP Artifact Binding. We send a random token, which acts as an identifier to retrieve the document via a back channel.
The first two of these can have some serious implementation issues.
Identifying SAML Responses #
As described previously, SAML responses are generally passed either in the URL like this:
or in the body of a POST request like this:
Both of these forms can be manipulated by an attacking user as it passes through their browser. If, on the other hand, you see a SAML Artifact like this:
then there’s probably not much you can do with it as an attacker. These tokens are resolved into the original messages and then retrieved via a back-channel, so unless you have access to the target’s private network (and likely an SSL/TLS implementation bug while you’re at it), these are pretty useless to an attacker.
Protecting Messages in Transit #
The issue here is that in both the HTTP Redirect and HTTP POST bindings, the document from the IdP validating the user’s identity is passed through that user’s browser, and hence may be tampered with in transit. The HTTP Artifact Binding is immune to this particular issue.
If these messages lack any protections, an attacker could simply modify the response to, for example, claim to be somebody else. If I log in to the IdP as “Tim” then I could simply alter the response document to claim to be “Emmanuel” instead. In fact, I could just entirely forge the response, become Emmanuel, and impersonate him.
Of course, the authors of the standard aren’t lax enough to let that slip past them - they’ve tried very hard to fix this problem. The solution in the standard is to attach an XML Signature to each message, protecting that message against tampering.
The XML Signature Standard #
The XML Signature standard is an immensely complicated beast, designed by a working group involving all the big names, and intended to be a one-size-fits-all solution to building tamper-resistant XML documents. Unfortunately, as is often the case, one-size-fits-all becomes the-only-size-fits-nobody.
In a normal application of digital signatures, we take a document to be signed, run it through a cryptographic hash function, and then apply a digital signature algorithm to the hash. If the document received is exactly identical, the signature will validate, whereas if even a single bit changes, the signature becomes invalid and the document is rejected.
Unfortunately, XML Signatures have one killer feature - the standard allows us to sign part of the document instead of the whole document, and embed the signature within the same document it’s supposed to be validating - so called inline signatures. The signatures do this by containing a “Reference” to the part of the document they sign, usually by referring to the “ID” attribute of an XML element, but in theory allowing anything in the XPath standard to be used as an expression. I can, in theory, write a signature anywhere within a document that refers to the “third to last <foo> element”, or equally vague expressions.
When validating an XML signature it’s not enough to ask the question “is this a valid signature from this signer?”. We also have to ask “is this signature present, referring to the right part of the document, applying all the right canonicalizations, from the expected signer AND valid?”. All too often, at least one of these checks is not implemented.
Getting Started with SAML Raider #
While all attacks described here can be carried out without many tools, SAML Raider, a Burp proxy plugin, is a useful tool for testing the common cases.
SAML2 Burp Extension
As described above, signatures can appear in various places within the SAML message and cover various parts of the message. By keeping the content of the message but adding new parts and modifying the structure of the remaining parts, we can craft messages that are still technically signed correctly, but may be interpreted by SAML libraries as having crucial parts signed when they are not.
Whenever the Service Provider is supposed to check something, there’s an opportunity for them to fail to do so or do so incorrectly, giving us an opportunity to bypass the signature. Enable Burp’s interception, capture the SAML request, and try these transformations. Each one should be done against a fresh, valid log-in attempt, as there is usually a nonce preventing us from replaying the same request repeatedly.
For repeated attempts, you may benefit from intercepting a single endpoint only in Burp using interception options like this:
Is a Signature Required? #
The SAML standard requires that all messages passed through insecure channels, such as the user’s browser, be signed. However, messages that pass through secure channels, such as an SSL/TLS back channel, do not have to be. As a result of this, we’ve seen SAML consumers that validate any signature present, however silently skip validation if the signature is removed. The software is essentially presuming that we’ve already checked that a message coming from an insecure channel is signed, when this isn’t the case.
The impact of this is the ability to simply remove signatures, and tamper with the response as if they weren’t there. SAML raider can test this one pretty easily:
Is the Signature Validated? #
Validating XML signatures is extremely complicated, as the standard expects a series of transformation and canonicalization steps to be applied first (e.g. to ignore the amount of white space). The difficulty of this makes it extremely hard to validate signatures without a fully featured XML Signature library behind you. The impacts of this are:
- Developers don’t generally understand the internals of signature validation.
- Intermediate tools, such as web application firewalls, have no idea whether signatures are valid or not.
- Libraries may have configurable options, such as lists of permitted canonicalization methods, which are meaningless to the developer.
The difficulty of implementing this standard, and the somewhat arcane nature of it, leads to the issues we will now look at.
First, testing whether the signature is validated at all is simple - change something in the supposedly signed content and see if it breaks.
Is the Signature From The Right Signer? #
Another stumbling block is whether or not the receiver checks the identity of the signer. We haven’t seen this one done wrong, but SAML Raider will make this fairly easy to test.
Copy the certificate to SAML Raider’s certificate store:
Save and self-sign the certificate, so we have a self-signed copy of the same certificate:
Now we can re-sign the original request with our new certificate, either by signing the whole message or the assertion:
You could identify which of those two options is normally used by your recipient, or just try each of them.
Is the Correct Part of the Response Signed? #
How XSW Attacks Work #
The SAML standard allows signatures to appear in two places only:
- A signature within a
<Response>tag, signing the Response tag and its descendants.
- A signature within an
<Assertion>tag, signing the Assertion tag and its descendants.
The SAML standard is very specific about where signatures are allowed to be, and what they are allowed to refer to.
However, nobody implements XML signatures in all their gory complexity for use in SAML alone. The standard is generic, and so are the implementations and software libraries built for it. As a result, the separation of responsibilities looks like this:
- The XML Signature library validates according to the XML Signature standard, which allows anything to be signed from anywhere.
- The SAML library expects the XML Signature library to tell it whether or not the response is valid.
Somewhere in the middle of these two components, the rules about what we have to sign are often lost. As a result, we can often have our signature refer to a different part of the document, and still appear valid to the recipient.
By copying the signed parts of the document, and ensuring the signatures point to the copies, we can separate the part of the document that the XML Signature library checks from the part of the document that the SAML library consumes.
Automated XSW #
SAML Raider will automate the most common attacks of this form for you:
Try selecting each of those options from the drop-down, clicking “Apply XSW” and sending the request on. If this doesn’t cause an error, try doing it again and changing the username or other user identifier in each place it appears in the SAML XML.
Limitations of SAML Raider #
While SAML Raider will test the common cases, there are a few attacks that require a deeper understanding:
- Producing a response that will validate against an XML schema (requires hiding the shadow copy inside an element that may contain
- Bypassing validation when both the Response, and Assertions within it are signed and checked.
- Bypassing XML signatures in non-SAML contexts, for example SOAP endpoints using WS-Security extensions.
Manual XSW #
If SAML Raider’s out-of-the-box options don’t work, you may want to try a manual approach:
- Decode the Base64-encoded content to access the SAML Response XML.
- Check that the signature’s
<Reference>tag contains the ID of a signed element.
- Copy the signed content somewhere else in the document (often the end of the
<Response>is OK, if XML Schema validation is in play try finding somewhere to copy it that doesn’t break the schema.)
- Remove the XML signature from the copy, leaving it in the original. This is necessary as the XML encapsulated signature standard removes the signature that is being validated. In the original document, this was the contained signature, so we have to cut it out from the copy.
- Change the ID of the original signed element to something different (e.g. change a letter).
- Change the content of the original assertion.
- Re-encode as Base64, put in to the request and forward on.
If the signature validation points to the copy, it will ignore your changes. With practice, you can complete this process pretty quickly if strict time limits on requests are in play.
SAML Pentest Checklist #
- Does the SAML response pass through the web browser?
- Is it signed? If not, try changing the content.
- Is it accepted if we remove the signatures?
- Is it accepted if we re-sign it with a different certificate?
- Do any of the eight transforms baked in to SAML Raider produce a result that is accepted?
- If you change such a response, is the modified response accepted?
- Might one of SAML Raider’s limitations noted above apply? If so, you may need to try a manual approach.