The initial research for this post started with an attack vector as opposed to a real issue: ruby-saml used two different SAML libraries. During signature verification, the element is first read using the REXML parser and with Nokogiri's XML parser. If a difference could be used to trick the XPath query then it may be possible to bypass the signature verification.
Security Assertion Markup Language (SAML) is a framework for transporting signed-in user from an identity provider to a service provider in XML. The main part of the SAML response is the Assertion. This contains a DigestValue, SignatureValue and a Subject field for the user. Normally, the entire assertion portion is canonicalized and then compared again the Digest. Then, a signature is compared on the SignatureValue based upon this digest. These are important later for exploitation.
Before looking into parser differentials, they first needed to see if there was a path to exploitation. After carefully analyzing which parser makes what query, the came to the following conclusions:
- The SAML assertion is extracted and canonicalized with Nokogiri. The hash is then compared with a value from REXML.
- The SignedInfo element is extracted and canonicalized with Nokogiri - it is then verified against the SignatureValue, which was grabbed with REXML.
What's the path for this exploit? First, make it so that REXML doesn't find the Signature XML object but Nokogiri does. This SignedInfo is then compared against the SignatureValue extracted via REXML. Later on, the DigestValue needs to be compared with the one that was signed. By getting Nokogiri to canonicalize the assertion but get REXML to extract a different DigestValue than the one used on signature validation, it will bypass the check without being signed.
There were two known PoCs. This was initially found via a HackerOne submission using techniques from
XML roundtrips vulnerabilities, since GitHub had added this library to their bounty. The author of the article found a different parsing issue from using the Trail of Bits Ruby fuzzer ruzzy.
There is a nice image that explains this but I'm unsure whose exploit it is. In the image, the Signature field found by Nokigiri is NOT under an Assertion. When REXML needs the digest, it uses the one under Assertion instead. Seems like the traversing of the tree was funky between the libraries.
Relying on two parsers is error prone. Exploitability isn't automatic in these cases but it's a good place to start. To fix this issue, they decided to use only a single library, which is great. Solid article but I wish both exploits were explained in more detail.