Accueil > OpenID Connect OAuth Serveur dédié > Développer > Vérification de l’origine de la requête reçue par un serveur de (...)

Vérification de l’origine de la requête reçue par un serveur de ressource

La transmission de jetons à des serveurs de ressource (RS) implique que ces derniers soient réceptifs à des jetons de toute provenance. Cela présente une opportunité pour un attaquant (une application étrangère) d’exploiter un jeton compromis ou volé.

Une fois de plus, l’analyse montre que nous ne sommes en mesure d’assurer pleinement la sécurité des données que dans le cas du flux "Authorization Code" pour des applications "avec back-end".

Considération sur la sécurité de la transmission des jetons aux serveurs de ressources

La proposition de standard RFC 6749 est bien claire sur la nécessité de n’utiliser les jetons d’accès que dans un espace de parties autorisées (§ 10.3.) :

Les identifiants de jeton d’accès (ainsi que tous les attributs de jeton d’accès confidentiels) DOIVENT être gardés confidentiels pendant le transit et le stockage, et partagés uniquement entre le serveur d’autorisation, les serveurs de ressources pour lesquels le jeton d’accès est valide et le client auquel le jeton d’accès est émis.

Si tous les serveurs de ressources (RS) auxquels sont transmis les jetons appartiennent à une même organisation (corporate realm), on peut supposer que les ressources sont situées dans un sous-réseau protégé par un pare-feu interdisant les requêtes depuis des applications extérieures au sous-réseau. C’est ce qui est désigné par "espace de confiance".
On peut alors :
- espérer que les jetons ne seront pas compromis, c’est à dire accessibles à des applications étrangères à l’organisation,
- supposer que toute application située dans ce sous-réseau et s’adressant aux ressources de ce même sous-réseau est autorisée à en recevoir les réponses.

Dans le contexte OpenID, un client est dénommé Relying Party (RP) ou "partie de confiance". Du point de vue de l’utilisateur final, il lui est demandé de faire confiance à l’application qui lui demande son autorisation : c’est à lui d’identifier l’application ... ou de se faire hameçonner ! Du point de vue d’un serveur de ressource, la confiance ne résulte pas d’un caractère particulier de l’application que ce serveur pourrait constater par lui-même, mais du fait qu’elle se situe à l’intérieur d’un espace de confiance que l’on suppose inaccessible aux autres applications.

Cependant, même dans le cadre d’un client bien identifié, une attaque bien élaborée pourrait conduire à voler le jeton pour l’exploiter avec une application étrangère.

De toutes façons, les suppositions ne sont jamais très bonnes en matière de sécurité [1].

Bien entendu, si on adresse un jeton à une ressource étrangère, il faut considérer le jeton comme compromis, car il pourra être réutilisé par une application étrangère.

La problématique de l’identification des applications est développée ici : Typologie des applications au regard de la sécurité des données. On y voit que le flux Authorization Code, s’il permet d’identifier les applications "avec back-end", ne le permet pas dans le cas des applications "sans back-end". Il existe des méthodes complémentaires qui réduisent le risque [3] d’un vol de jeton, mais qui, cependant, ne permettent pas d’authentifier une application cliente sans back-end.

Identifier l’application cliente

Plutôt que se reposer sur la notion d’espace et d’applications de confiance, le serveur de ressource (Resource Server) doit pouvoir vérifier la légitimité du détenteur du token.

Mais comment ?

La spécification décrit clairement cette limitation (§10.3) :

Cette spécification ne fournit aucune méthode pour que le serveur de ressources s’assure qu’un jeton d’accès qui lui est présenté par un client donné a été émis pour ce client par le serveur d’autorisation.

 [2].

Dans le protocole HTTP, une ressource répondra à l’URL origine de la demande.

Deux types d’applications doivent être distinguées s’agissant de l’origine de la demande [3] :
- Les applications "avec back-end", c’est à dire émettant leurs requêtes depuis un serveur : la requête sera émise avec l’IP du serveur de l’application.
- Les applications "sans back-end", émettant leur requête à partir d’un user-agent (du code Javascript exécuté par le navigateur de l’utilisateur final, le système d’exploitation de l’ordinateur hôte d’une application native etc.) : la requête sera émise avec l’IP de la connexion de l’user-agent, éventuellement masquée par un proxy.

Dans l’état actuel du développement d’OpenID Connect, nous ne considérerons que les "applications avec back-end" dont l’IP du serveur hôte garantit l’identité [4]. Autrement dit :

OpenID Connect n’est en mesure d’assurer la sécurité des données que dans le cas du flux "Authorization Code" pour des applications "avec back-end".

OAuthSD offre deux moyens de vérifier la légitimité d’une application "avec back-end" présentant le jeton en se fondant sur l’IP de l’origine :
- par introspection avec l’IP du demandeur,
- avec les déclarations supplémentaires du jeton JWT ’cip_hash’ ou ’cip’.

Les IP de l’application cliente, auxquelles sera comparée l’IP du demandeur, sont déterminées de la façon suivante :
- si l’application cliente est enregistrée avec un champ ’ip’ de la table ’clients’ non vide, OAuthSD utilisera ces IP(s),
- sinon, les IP(s) seront obtenues en interrogeant le DNS avec le nom d’hôte de l’URL de retour (redirect URI) enregistrée pour l’application [5] [6].
OAuthSD permet de prendre en compte un éventuel proxy (de confiance) pour accéder à la véritable IP de l’application.

Vérification par introspection avec l’IP du demandeur

Le projet de RFC 7662 évoque le sujet de façon implicite. Il est en effet indiqué dans la section 2.1 :

"Le point de terminaison d’introspection PEUT accepter d’autres paramètres OPTIONNELS pour fournir un contexte supplémentaire à la requête. Par exemple, un serveur d’autorisation peut souhaiter connaître l’adresse IP du client accédant à la ressource protégée pour déterminer si le client présentant le jeton est le bon. La définition de ce paramètre ou de tout autre paramètre sort du cadre de la présente spécification, ... "

OAuthSD met ceci en œuvre de la manière suivante :

Si le RS appelant le point de terminaison Introspection transmet l’adresse IP de son propre demandeur au moyen du paramètre ’requester_ip’, l’introspection vérifie que cette adresse IP se trouve dans le même sous-réseau que l’application cliente identifiée par la déclaration ’aud’.

Afin d’éviter de répondre à un malware une ressource protégée recevant un jeton d’identité doit le valider par introspection en transmettant le paramètre ’requester_ip’.

Sur OAuthSD, pour que cette vérification soit effective, il convient de :
- renseigner l’IP des applications clientes lors de leur enregistrement (à défaut l’IP du domaine de l’URI de retour sera utilisé),
- de régler à ’true’ la constante de configuration CHECK_CLIENT_IP.

Notes :
- D’une façon générale, l’IP des applications faisant appel aux contrôleurs Introspect ou UserInfo est vérifiée comme indiqué ci-dessus, sans qu’il soit nécessaire de fournir le paramètre ’requester_ip’, afin de vérifier qu’il s’agit bien de l’application cliente enregistrée.
On comprendra que, dans le cas d’une demande effectuée à un serveur de ressource qui fait appel au contrôleur Introspect, il est nécessaire que le serveur de ressource fournisse l’IP du demandeur, sinon ce serait l’IP du serveur de ressource qui serait contrôlée, et non celle du demandeur.
- Notons qu’OAuthSD offre également une option permettant la vérification de l’appartenance à un même domaine ou sous-domaine (voir la constante de configuration CHECK_SAME_DOMAINS) effectuée par le contrôleur Introspect.

Exemples d’appel à l’introspection avec le paramètre ’requester_ip’

Par en-tête :

PHP

  1. // Method Bearer + parameters by Post
  2. $data1 = array(
  3.     'requester_ip' => $_SERVER['REMOTE_ADDR'],  
  4. );
  5.  
  6. $h = curl_init($introspection_endpoint);
  7. curl_setopt($h, CURLOPT_HTTPHEADER, array(
  8.     'Content-Type: application/x-www-form-urlencoded',
  9.     'Authorization: Bearer ' . $res1['id_token']
  10. ));
  11. curl_setopt($h, CURLOPT_RETURNTRANSFER, true);
  12. curl_setopt($h, CURLOPT_POST, 1);  
  13. curl_setopt($h, CURLOPT_POSTFIELDS, http_build_query($data1));
  14.  
  15. $res = curl_exec($h);
  16.  
  17. ...

Télécharger

Par Post :
PHP

  1. // Post Methode  
  2. $data1 = array(
  3. 'token' => $res1['id_token'],
  4. 'requester_ip' => $_SERVER['SERVER_ADDR'],  
  5. );
  6.  
  7. $trace = $_SESSION['trace'];
  8. $trace .= '----- Step 3 : JWT Introspection -----' . "<br />";
  9. $trace .= 'access token : ' . $access_token . "<br />";
  10. $trace .= 'data : ' . print_r($data1,true) . "<br /><br />";
  11. $_SESSION['trace'] = $trace;    
  12.  
  13. $h = curl_init($introspection_endpoint);
  14. curl_setopt($h, CURLOPT_RETURNTRANSFER, true);
  15. curl_setopt($h, CURLOPT_TIMEOUT, 100);    //100 : DEBUG
  16. curl_setopt($h, CURLOPT_POST, true);
  17. curl_setopt($h, CURLOPT_SSL_VERIFYPEER, false);
  18. curl_setopt($h, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));  
  19. curl_setopt($h, CURLOPT_POSTFIELDS, http_build_query($data1));
  20.  
  21. $res = curl_exec($h);
  22.  
  23. ...

Télécharger

Voyez également :
- API Open ID Connect : Introspection (Introspection Endpoint),
- Plus loin avec l’Introspection,
- Envoi d’une requête à un serveur de ressource (RS).

Vérification avec les déclarations supplémentaires du jeton d’identité ’cip_hash’ ou ’cip’

OAuthSD introduit dans la charge utile du jeton JWT une déclaration ’cip’ ou ’cip_hash’ pour permettre la vérification de l’IP du demandeur [7] [8].

Cette vérification pourra être effectuée localement, ou par l’introspection, appelée comme ci-dessus avec le paramètre ’requester_ip’.

’cip_hash’
Dans le cas général, une application n’a qu’une IP. Dans ce cas, c’est cette déclaration qui sera générée, et non ’cip’, avec l’avantage de masquer l’IP de l’application cliente en la transmettant sous la forme d’un hash. Si l’application a plusieurs IP, et si la constante de configuration FORCE_CIP_HASH est true, la première IP de la liste sera utilisée pour générer la déclaration.

’cip’
La déclaration ’cip’ est générée lorsque les IP de l’application clientes sont multiples. Sa valeur est une suite d’adresses IPv4 séparées par un espace.
Cependant, si la constante de configuration FORCE_CIP_HASH est true, la déclaration ’cip_hash’ sera toujours émise à la place.

Notes :
- L’une ou l’autre de ces déclarations supplémentaires sera toujours ajoutée à la charge utile du jeton d’identité JWT du flux OpenID Connect Authorization Code, mais pas dans son pendant OAuth 2.0.
- Cette méthode a l’avantage de permettre la vérification par la ressource protégée (rappelons que la charge utile du jeton JWT est simplement encodée URL64). Ceci est utile pour bloquer une attaque sans avoir à faire appel à l’introspection, ou quand la validation du jeton JWT est effectuée localement sans passer par l’introspection.
- Le contrôleur Introspect effectuera également la vérification de l’IP avec ces déclarations. Dans le cas d’un serveur de ressource, il est nécessaire que ce dernier transmettre le paramètre ’requester_ip’.
- Un proxy peut se situer entre l’application cliente et le serveur d’authentification. Les paquets adressés par le proxy portent l’IP du proxy, et non l’IP de l’hôte de l’application cliente. Dans ce cas, la vérification par l’IP va échouer, sauf dans le cas d’un "proxy de confiance" traité ci-après.

Autre méthode : avec la déclaration audience du jeton JWT

La bibliothèque oauth2-server-php réduit "aud" à l’identifiant du client client_id.

Pourtant la spécification prévoit :

"Il PEUT aussi contenir des identifiants pour d’autres audiences. Dans le cas général, la valeur aud est un tableau de chaînes sensibles à la casse".

Certaines implémentation d’OpenID Connect considèrent que la déclaration audience relève du niveau applicatif, ce qui signifie que sa valeur est une convention établie au sein d’une même organisation (corporate realm) entre l’application et le serveur de ressource.

Dans cette optique, la déclaration "aud" est un tableau qui peut contenir les IPs des applications autorisées à présenter le jeton. Ceci permettra au serveur de ressource de vérifier directement l’IP et ainsi bloquer les malwares sans avoir à émettre une demande d’introspection.

La déclaration audience étant définie au niveau de la bibliothèque oauth2-server-php, nous devons modifier celle-ci pour intégrer l’IP de l’application cliente (fournie lors de l’inscription de l’application sur le serveur) dans la déclaration "aud". Ce développement est en cours.

Prise en compte d’un "proxy de confiance"

La question est de savoir si on peut faire confiance à la déclaration HTTP_X_FORWARDED_FOR par laquelle un proxy retransmet l’IP de la requête d’origine.

Un éventuel proxy situé entre le réseau Internet et le serveur d’authentification est maîtrisé par l’organisation et peut être considéré comme étant de confiance.

OAuthSD permet de désigner ces proxys par leur IP dans la constante de configuration TRUSTED_PROXIES. Si la constante de configuration USE_PROXY est true, OAuthSD remplacera ces IP par celle fournie par la déclaration HTTP_X_FORWARDED_FOR.

Notes :
- Parmi les proxies de confiance on peut trouver les proxies inverses (reverse proxy) dont la fonction est de fournir un système de cache. Le principe même de l’authentification exclut l’usage de cache.
- Parmi les proxies de confiance se trouvent également les équilibreurs de charge (load balancer). Un "bon" équilibreur de charge conserve l’adresse IP du client d’origine en l’insérant dans l’en-tête HTTP adressée au backend.
- Il existe un protocole permettant de communiquer l’IP du client entre proxies, voir : https://www.haproxy.com/fr/blog/preserve-source-ip-address-despite-reverse-proxies/.

SAFEIP : Élimination des "proxies privés"

On notera que cette notion de proxy de confiance est l’inverse de ce qu’un grand nombre d’internautes désignent sous le vocable de proxy dont ils attendent qu’ils masquent leur adresse IP. De tels proxies se disent "anonymes" et sont désignés sous le vocable "proxy privé" ou "private proxy" ce qui n’a rien à voir avec le fait qu’ils soient privés, mais qu’ils ont pour but d’assurer l’anonymat de l’internaute. L’appellation "anonymizing proxy" traduit mieux la fonction de tels proxies.

OAuthSD comporte une fonctionnalité "SAFEIP" permettant de bloquer les adresses IP douteuses, parmi lesquelles se trouvent la plupart de celles de tels proxies.

OAuthSD offre ainsi deux mécanismes complémentaires : La prise en compte des proxies de confiance permet d’autoriser les proxies de l’interface réseau local / Web, tandis que la fonction "SAFEIP" permettra de bloquer les proxies douteux du Web.

Notes

[1C’est un euphémisme pour ne pas dire que nous sommes en présence d’une faille de sécurité béante qui n’est pas assez prise en considération.

[2Peut-on trouver des solutions dans d’autres documents ? Hélas, non ! On peut lire dans le document OAuth 2.0 Token Exchange :

Bien que quelques nouvelles déclarations JWT soient définies pour permettre l’expression de la sémantique de la délégation, la syntaxe, la sémantique et les caractéristiques de sécurité spécifiques des jetons eux-mêmes (à la fois ceux présentés au serveur d’autorisation et ceux obtenus par le client) sont explicitement hors de portée et aucune exigence n’est formulée sur le modèle de confiance dans lequel une implémentation peut être déployée.
Des profils supplémentaires peuvent fournir des exigences plus détaillées concernant la nature spécifique des parties et la confiance impliquées, par exemple si la signature et / ou le cryptage des jetons est nécessaire ou si des jetons de type preuve de possession seront nécessaires ou émis ; Cependant, ces détails seront souvent des décisions de politique prises en fonction des besoins spécifiques de déploiements individuels et seront configurés ou mis en œuvre en conséquence.
.

[3On classe souvent une application "avec backend" comme "application confidentielle" ou "privée" et une application "sans back-end" comme "application publique". C’est une confusion, voir : Typologie des applications au regard de la sécurité des données.

[4On peut parler "d’IP de l’application" mais notons que plusieurs applications pourraient partager une même IP.

[5Il convient donc de s’assurer de l’intégrité des DNS. Le mieux serait que les DNS des applications et le DNS auquel accède le serveur (un DNS local de préférence) se trouvent dans un espace de confiance, et/ou qu’ils soient protégés par DNSSEC.

[6Cette méthode peut introduire un délai de réponse de la ressource indésirable, raison pour laquelle OAuthSD propose l’enregistrement de l’IP.

[7Nous vantons l’intérêt du jeton d’Identité au format JWT signé pour lier l’identité de l’utilisateur final, celle de l’application et les portées d’autorisation. Cependant, un serveur de ressource HTTP ne peut identifier l’origine de la requête que par son IP dans la relation serveur-serveur. L’identifiant du client transmis par le jeton n’est pas probant puisque le jeton peut avoir été "volé". Il est donc indispensable d’incorporer au jeton d’identité l’IP de l’application légitime afin qu’un serveur de ressource puisse la vérifier.

[8Peut-on utiliser la déclaration ’aud’ pour transmettre cette URL ? Cela semble le cas si l’on s’en tient à la spécification du JWT pour laquelle ’aud’ peut être une liste d’URI. Mais la spécification OpenID Connect est plus restrictive :

aud
OBLIGATOIRE. Audience (s) pour laquelle ce jeton d’identifiant est destiné. Il DOIT contenir le client_id OAuth 2.0 de la partie de confiance en tant que valeur d’audience. Il PEUT aussi contenir des identifiants pour d’autres audiences. Dans le cas général, la valeur aud est un tableau de chaînes sensibles à la casse. Dans le cas spécial usuel où il y a un public, la valeur aud PEUT être une chaîne sensible à la casse.

En conséquence, la bibliothèque oauth2-server-php fixe la valeur du paramètre ’aud’ à l’identifiant de l’application cliente. La déclaration ’aud’ n’est pas le bon choix pour permettre à OAuthSD de transmettre l’IP du client.