Rick Osgood | July 13, 2018
Exploiting a SAML Implementation
During a recent web application test, I discovered a bug in a Security Assertion Markup Language (SAML) implementation. This bug involved an insecure implementation of a SAML feature combined with a custom authentication mechanism our client developed out of a need to support their customers. With a bit of creative thinking, we demonstrated how these insecure configurations could combine to form a tricky phishing attack that would allow an attacker access to secured pages potentially containing sensitive information.
The client’s web application allowed customers to create simple web pages and web forms. Customers used the application to collect information from end users and as a portal to contain human resources documentation and other potentially sensitive materials. Customers protected these pages in various ways. For example, they password-protected pages only allowing access to authorized users. The application also supported single sign-on (SSO) using SAML. Testing these authentication mechanisms is important because secure configuration can be tricky.
I logged into the application using an admin account and created a simple page. I then added some basic text content that read, “If you can see this, good for you.” Finally, I changed the configuration to require SSO authentication. I named the page “samlpage” and saved the settings, which generated a new URL:
While attempting to load the page with an unauthenticated browser session, I received the following message.
After a few seconds, the page redirected to our single sign-on page. The SAML identity provider (IDP) is Anitian’s installation of SimpleSAMLphp set up specifically to test SAML implementations. The webpage redirected me to the following login.
An incorrect password failed to authenticate and I remained stuck at the login page. If I entered valid credentials, I was redirected back to our client’s application. After a couple of redirects, I successfully authenticated to the app and could now access the SAML-protected page.
The basic functionality worked as expected.
I started out using the SAMLRaider plugin for Burpsuite since it made some of the standard tests, like signature modifications, easy to perform.
During my analysis, most basic SAMLRaider tests failed. I did discover the ability to replay the SAML response from the IDP to the client’s application, or service provider (SP). SAML responses should only be valid for a single use. During this test, responses were accepted repeatedly resulting in multiple valid sessions under the same user account. If an attacker intercepts the SAML response, they can open their own session and bypass authentication. This attack is similar to obtaining user passwords, but worse as the hacker can bypass multi-factor authentication mechanisms that the IDP may use.
I dove deeper into the SAML traffic passing back and forth between the SP and the IDP. When the SP redirected the user to the IDP for authentication, the GET parameters included a single parameter for the SAML assertion and a second parameter called RelayState.
This RelayState parameter contained a URL leading to a page within the client’s application. When converted from URL decoding, it looked like this:
This is the URL of the protected page I created, though it included an extra parameter called “sp_id.” The SP sent this URL to the IDP in its own parameter. After logging into the IDP, the user gets redirected back to the SP (our client). The IDP transmitted the SAML response back to the SP along with the RelayState parameter again, which was unaltered and effectively echoed back to the SP.
The SAML response was posted to the /sso/saml/acs/73 page and the user was redirected to the protected page at /samlpage.
If you look at the Burp logs, you will see the SAML response POSTed back (purple line). The very next request is for the protected resource, which is like the URL in the RelayState parameter, but instead of looking like this:
It looked like this:
https://clientwebsite.com/samlpage?saml_token=<really long string>
I couldn’t decode the saml_token parameter, but based on the name, it looked important. I thought maybe it was being used to permit access to the protected samlpage page. As a test, I copied the URL with the long saml_token parameter and tried opening it up in an unauthenticated browser session. Surprisingly, it granted me access to the protected page. I did not get redirected to the IDP, and I did not have to log in. If I had a valid saml_token value, I was allowed access to view the page, which was intriguing.
The next question I wanted to answer was the reasoning for the RelayState value. At first glance, the value appeared to instruct the SP where to redirect the user. However, this interaction was not initially clear because the sp_id parameter did not pass through. The test was important because if I modified the parameter and redirected users to any website, then I would have an open redirection vulnerability on my hands.
I logged out of the application and initiated the SSO process again for further testing. I intercepted the original SAML request to the IDP and modified the RelayState value to a different URL. I chose a webserver I had control over so instead of looking like this:
I changed it to this:
I then forwarded the request off to the IDP. The login page loaded, and I logged in with a known-valid account. The IDP then redirected me back to the SP. I inspected the request containing the SAML response and noticed the RelayState parameter still contained my modified value. The client application authenticated the SAML response and was supposed to redirect me to the protected page. Instead, I was redirected to this URL, which meant there was a redirect vulnerability:
https://anitianwebsite.com/owned?saml_token=<long value here>
Next, I used the client’s application to generate a SAML assertion for another user account. I modified the RelayState parameter and copied the entire URL. I never visited the IDP URL but could send it to a victim user. The link directs the victim to the IDP. Victim authentication to the IDP redirects them back to our client’s application and to any domain I want.
I could perform numerous attacks including hooking their browser with something like BEEF, or setting up a phishing page to steal their credentials.
Looking again at the URL I ended up at, I noticed the saml_token was transmitted to my attacker web server. I checked the access logs and found the following information:
I had the saml_token, which made this vulnerability more serious. The saml_token parameter acted as a key to reach the protected page. I no longer needed the user’s password or even a SAML response. I just needed the token to obtain access to the protected page.
To verify this, I ran through the following steps:
As the attacker
- I attempted to visit the protected page.
- I was redirected to the IDP, but I intercepted the request and did not allow it to complete.
- Instead, I copied the URL and edited the RelayState to contain my own web server URL.
- I then copied the resulting URL and emailed it to Victim.
As the victim
- The victim clicked the link in their email, which took them to their legitimate IDP login page.
- The webpage was legitimate, so the victim entered their username and password to authenticate.
- The victim was redirected to the SP, which then redirected them to the attacker’s webpage.
As the attacker
- I checked my web server access logs and copied the saml_token value.
- I then took the original RelayState URL and added the saml_token value to the end.
- I entered that URL into my browser and accessed the protected page.
This vulnerability has a potentially high impact because some of our client’s customers may store sensitive information on these protected pages.
This type of vulnerability cannot be detected using any current automated scanning tools. This interesting finding demonstrates the unique bugs we can find as part of a manual penetration test. It applies specifically to this one client as it was something they developed in-house to solve their own unique problem.
Fixing the bug
Validation of the RelayState parameter was the real problem and needed fixing. Our client can modify their application to ensure that parameter always points to their own domain. Another option is to place a single-use token in that parameter. The token could then be placed into a lookup table with an associated URL. This way when the application receives the RelayState token, they can just look up what URL that token responds to and redirect to that page. This would prevent an attacker from being able to redirect users to external domains and avoid having to use tricky matching rules to ensure a URL is valid.
I learned during our phone conversation that the saml_token was valid only for a 30-second window. This helped to mitigate the risk somewhat because the attacker would have to act in real-time or otherwise use automation to abuse the token immediately after receiving it. Better still would be to ensure the saml_token can only be used once. This way if an attacker obtained the token from someone’s browser history or a proxy log or similar, it would be of no use to them.