Dis Keycloack c’est grave si je laisse traîner mon client secret ?

Benjamin Buffet
7 min readMar 1, 2024

--

Il y a quelques temps j’ai eu un échange qui ressemblait à peu près à ça :

Interlocuteur : “A quel point est-il dangereux d’exposer un client secret ?”
Moi: “c’est mal”

En règle générale, j’aurai tendance à dire que si des gens prennent la peine de mettre “secret” dans le nom d’un attribut c’est qu’il doit y avoir une raison 😅

Mais en y repensant par la suite, je me suis rendu compte que j’avais répondu de façon instinctive, en me basant sur les recommandations que j’avais pu lire certes, mais sans avoir cherché à savoir ce que l’on risquait à le laisser traîner.

Aujourd’hui on va donc s’intéresser à ce que pourrait faire une tierce personne qui serait en possession d’un client secret et ainsi répondre à la question :

Est-ce que c’est grave si je laisse traîner mon client secret ?

Spoiler alert

oui

Avant propos

Tout au long de cet article je ne parlerai que des clients de type Openid Connect, même si l’on peut retrouver la notion de client secret pour les clients SAML2.

Par ailleurs, je ne parlerai bien évidemment que de clients confidentiels et non public puisque ces derniers ne possèdent pas de client secret.

Pour pouvoir faire quelques tests d’appels j’ai configuré un serveur keycloak, exécuté en mode développement. Ce serveur contient un royaume realm1 et un client myapp.

Configuration initiale du client

Enfin je ne suis pas pentester, et je n’ai pas leur imagination en terme de découverte de failles. Je vais donc ici vous donner mon analyse de développeur et les problèmes immédiats que je peux anticiper.

Mais avant tout le client secret c’est quoi ?

C’est un mot de passe , qui, associé au client id de l’application (myapp dans mon cas), va permettre à cette dernière de prouver son identité au serveur keycloak.

Le client secret n’est pas une spécificité du serveur keycloak, mais une des propriété définies dans la RFC 6749 OAuth 2.0, reprise par la suite par la norme Openid Connect.

Ce n’est pas le seul moyen pour un client de s’authentifier auprès du serveur. On peut cité également l’utilisation d’un JWT signé, d’une clé privée, ou les deux, mais cela ne rentre pas dans le cadre de cet article.

En terme de composition d’après la RFC 6749, c’est une chaine composée de charactères alpha numérique auxquels viennent s’ajouter les symboles suivants : !, ,“, #, $, %, &, ‘, (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, \, ], ^, _, `, {, |, }

Concernant le client id, contrairement au client secret, c’est une donnée public facilement trouvable ne serait-ce que parce que si vous utilisez l’authorization code flow, par exemple, il est passé en claire dans l’url de redirection vers keycloak :

http://mydomain.org/auth/realms/realm1/protocol/openid-connect/auth?client_id=myapp&redirect_uri=http://localhost/&state=0dfa5cf6-936c-4626-8b35-965150e8e5c8&response_type=code&scope=openid

Dans quel cas on s’en sert ?

On s’en sert principalement lors de la récupération des différents token, soit par exemple pour échanger le code lors d’un flux de type authorization code flow, ou bien encore échanger un refresh token contre un nouvel access token.

Quels sont les risques s’il se retrouve exposé ?

Pour répondre à cette question je vais m’appuyer sur un document qui recense les différents risques en matière de sécurité, à savoir la RFC 6819 : OAuth 2.0 Threat Model and Security Considerations. Encore une fois ces risques ne sont pas lié à keycloak en particulier, mais sont énoncés dans le cadre du protocole OAuth 2.0 en général.

A la section 4.1.1 on trouve les informations qui nous intéressent et dont voici une traduction (Faites pas ChatGPT) :

L’attaquant pourrait essayer d’accéder au secret d’un client en particulier afin de :

- rejouer ses jetons de rafraîchissement et ses “codes” d’autorisation, ou
- obtenir des jetons au nom du client attaqué avec les privilèges de ce “client_id” agissant comme une instance du client.

L’impact résultant serait le suivant :

- L’authentification du client pour l’accès au serveur d’autorisation peut être contournée.
- Les jetons de rafraîchissement volés ou les “codes” d’autorisation peuvent être rejoués.

On pourrait résumer ça en disant que les risques sont de deux natures :

L’usurpation de l’identité d’un utilisateur

L’usurpation de l’identité du client

Usurper de l’identité d’un utilisateur

Le but ici va être de récupérer un access token. Si l’on suit le protocole, il y a 2 cas dans lesquels le client secret rentre en jeu.

On va commencer par le plus simple : Le renouvellement d’un access token, grâce à un refresh token. Normalement, le refresh token est détenu par le backend et n’a pas été exposé au frontend (Cas d’un client confidentiel). Par contre même si il a normalement été envoyé au backend par keycloak via une connexion en TLS, il se peut que sur des réseaux interne les clients communiquent en http avec le serveur keycloak, rendant possible l’interception des requêtes, et qu’il en découle l’exploitation du refresh token. Si on considère que tous les échanges se font via des connexions TLS, il devient alors nettement plus difficile d’exploiter cette méthode.

Autre possibilité nécessitant un client secret, se servir de l’authorization code flow pour récupérer un access token. Dans ce cas encore on a le choix :

  • Initier une séquence d’autorisation frauduleuse
  • Utiliser un code généré dans une vraie séquence d’autorisation

Si l’on essaye d’initier une séquence frauduleuse, le point qui va être critique c’est le redirect uri. Si votre redirect uri est bien configurée le serveur Keycloak refusera toute tentative pour initier un flux d’autorisation pour un redirect uri qui ne correspond pas à celui que vous avez définie pour votre client. Il est même recommandé de bannir l’emplois de wildcards pour maximiser la sécurité.

Configuration des redirect URIs

Il faut être très vigilant sur ce point car keycloak nous autorise à laisser le champ Root URL vide et le champ Valid redirect URIs remplis avec le wildcard *. Dans ce cas n’importe qui peut utiliser les identifiants du client pour initier une séquence d’authentification.

Enfin si quelqu’un souhaite utiliser un code généré lors d’une séquence Authorization Code Grant, la difficulté sera de récupérer ce fameux code. En effet les autres arguments nécessaires sont le client_id qui est une donnée public, le redirect_uri qu’il est facile de récupérer également ainsi que le client_secret dont nous disposons. Je ne m’étendrai pas sur le sujet, mais il est apparemment possible mais pas trivial de récupérer ce code, via des extensions navigateurs malveillantes par exemple.

Usurper l’identité du client

On va maintenant aborder le cas qui me semble le plus risqué, car plus “facile”, l’usurpation de l’identité du client.

Avec keycloak vous avez l’option de donner au client la possibilité de se connecter comme le ferait n’importe quel utilisateur. Il suffit pour cela d’activer l’option Service accounts roles.

Au niveau OAuth 2.0 cela correspond au Client Credentials Grant. Si l’on dispose du client id et du client secret obtenir un access token est relativement simple :

curl \
-d "client_id=myapp" \
-d "client_secret=3GrV8C7D7bqw6k0nBcebwyRsDOyFo75y" \
-d "grant_type=client_credentials" \
"http://localhost:8081/auth/realms/realm1/protocol/openid-connect/token"

Et partir de là je vois 2 problèmes possible :

  • Le client possède des autorisations pour d’autres services
  • Le client possède des accès à l’API keycloak

Pour savoir ce que le client peut faire, il suffit juste d’ouvrir l’access token et de jeter un oeil aux claims aud et roles.

{
"exp": 1709312958,
"iat": 1709312658,
"jti": "81dc51f6-a898-40eb-a04d-71001c0ec631",
"iss": "http://localhost:8081/auth/realms/realm1",
"aud": [
"realm-management",
"account"
],
"sub": "02de96dd-19f1-4ed0-891a-b26757bf6f9d",
"typ": "Bearer",
"azp": "myapp",
"acr": "1",
"allowed-origins": [
"*"
],
"realm_access": {
"roles": [
"offline_access",
"default-roles-realm1",
"uma_authorization"
]
},
"resource_access": {
"realm-management": {
"roles": [
"manage-clients"
]
},
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "groups profile email",
"email_verified": false,
"clientHost": "172.18.0.1",
"preferred_username": "service-account-myapp",
"clientAddress": "172.18.0.1",
"client_id": "myapp"
}

Par exemple, dans l’acces token ci-dessus, mon client myapp dispose du rôle manage-clients, ce qui me permet, via le endpoint /admin/realms/realm1/clients, d’obtenir la liste des clients du royaume :

[
...
{
"id": "e6bb22ac-c0dd-40d4-bdcd-1cea1e7479a9",
"clientId": "client3",
"name": "Client 3",
"description": "",
"rootUrl": "http://localhost:5173",
"adminUrl": "http://localhost:5173",
"baseUrl": "/",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": [
"/*"
],
"webOrigins": [
"http://localhost:5173"
],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": false,
"publicClient": true,
"frontchannelLogout": true,
"protocol": "openid-connect",
"attributes": {
"oidc.ciba.grant.enabled": "false",
"display.on.consent.screen": "false",
"oauth2.device.authorization.grant.enabled": "false",
"backchannel.logout.session.required": "true",
"backchannel.logout.revoke.offline.tokens": "false"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": -1,
"defaultClientScopes": [
"web-origins",
"acr",
"profile",
"roles",
"groups",
"email"
],
"optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
],
"access": {
"view": true,
"configure": true,
"manage": true
}
}
...
]

Conclusion

Après ce tour d’horizon je pense qu’on peut conclure que oui, exposer le client secret n’est pas anodin, mais quele niveau de gravité va grandement dépendre de la configuration du client Keycloak. En effet le protocole OAuth 2.0 est suffisamment résilient et le serveur Keycloak suffisamment sécurisé pour limiter la gravité d’une telle situation.

Il faudra particulièrement veiller entre autre chose à :

  • Configurer des Redirect URIs le plus spécifiquement possible
  • N’activer que les grant flow essentiels
  • N’attribuer au client que les rôles nécessaires et suffisants

Par contre, si votre client accède à d’autres services, la vous devrez mettre en place d’autre solution de secours pour pallier à la fuite éventuelle du client secret.

--

--