Hack The Box :: Dab [write-up]

This is the first write-up of a series on Hack The Box systems penetration tests.

Dab is a Linux box released on August 18th 2018 and retired a few hours ago (on February 2nd 2019). The box IP address is 10.10.10.86 and the announced difficulty is hard.

Dab’s info card

This box involves a lot of enumeration, breaking and brute-forcing poor passwords. It shows that a development environment must be well secured and not published publicly, that software must be patched on a regular basis and that poor file permissions can lead to a server fully compromised.

Note: unless otherwise stated, all commands and scripts you will find below are run on macOSX. Especially sed and base64 syntax may slighly differ from Linux versions. Python2 is the preferred interpreter.

[1] Reconnaissance & Enumeration

Let’s start with an Nmap scan to see what the box has to offer:

$ sudo nmap -sS -sV --script=default,vuln -p- -T5 10.10.10.86 

PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.3
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_-rw-r--r-- 1 0 0 8803 Mar 26 2018 dab.jpg
| ftp-syst:
| STAT:
| FTP server status:
| Connected to ::ffff:10.10.13.239
| Logged in as ftp
| TYPE: ASCII
| No session bandwidth limit
| Session timeout in seconds is 300
| Control connection is plain text
| Data connections will be plain text
| At session startup, client count was 3
| vsFTPd 3.0.3 - secure, fast, stable
|_End of status
|_sslv2-drown:
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 20:05:77:1e:73:66:bb:1e:7d:46:0f:65:50:2c:f9:0e (RSA)
| 256 61:ae:15:23:fc:bc:bc:29:13:06:f2:10:e0:0e:da:a0 (ECDSA)
|_ 256 2d:35:96:4c:5e:dd:5c:c0:63:f0:dc:86:f1:b1:76:b5 (ED25519)
80/tcp open http nginx 1.10.3 (Ubuntu)
| http-csrf:
| Spidering limited to: maxdepth=3; maxpagecount=20; withinhost=10.10.10.86
| Found the following possible CSRF vulnerabilities:
|
| Path: http://10.10.10.86:80/login
| Form id:
|_ Form action:
|_http-dombased-xss: Couldn't find any DOM based XSS.
|_http-server-header: nginx/1.10.3 (Ubuntu)
|_http-stored-xss: Couldn't find any stored XSS vulnerabilities.
| http-title: Login
|_Requested resource was http://10.10.10.86/login
8080/tcp open http nginx 1.10.3 (Ubuntu)
|_http-csrf: Couldn't find any CSRF vulnerabilities.
|_http-dombased-xss: Couldn't find any DOM based XSS.
|_http-open-proxy: Proxy might be redirecting requests
|_http-server-header: nginx/1.10.3 (Ubuntu)
|_http-stored-xss: Couldn't find any stored XSS vulnerabilities.
|_http-title: Internal Dev

Note: read the command and flags explanation here.

We discover:

  • a vsFTPd 3.0.3 server on port 21 with anonymous access enabled and containing a dab.jpg file. There is no known public vulnerability for this version.
  • an OpenSSH 7.2p2 server on port 22. This version has some known vulnerabilities that allow users enumeration depending on the authentication scheme used. A Metasploit module is available.
  • an NGINX 1.10.3 web server serving 2 web applications on port 80 and 8080. Port 8080 seems to be used for dev purpose.

Nikto web scans on port 80 and 8080 do not report any additional and useful information. Let’s further analyze each service.

We can retrieve the image dab.jpg with any FTP client. It does not seem to contain anything of interest. EXIF data, LSB method and other frequent steganography techniques do not show anything.

dab.jpg

Let’s leave the users enumeration for later as it may not be necessary in the first place.

We are redirected to /login where we have a basic login form:

Dab web server port 80 landing page

A light directory enumeration does not find any other folder or file. When manually testing some username/password pairs, we can see some differences in the output error message depending on which username we use. For instance, with username admin or demo, the error message is:

Error: Login failed

For all other tested usernames, the error message is:

Error: Login failed.

See the missing dot at the end of the message? We keep this in mind for later as this could be as well used to enumerate users.

When browsing port 8080, we are welcomed with the following error message on an “Internal Dev” access:

Access denied: password authentication cookie not set

If we set the cookie password we get a new error message:

Access denied: password authentication cookie incorrect

A directory enumeration does not report anything more.

[2] Gaining Access

With all the above information let’s try to gain access to this box. The most interesting entry point seems to be the web application served on port 8080.

Let’s run the following script to brute-force the password with our preferred wordlist:

htb_dab_passbf.py

A few seconds later we get the following output:

PASSWORD => secret

By setting our new cookie, we get access to a TCP socket test interface where we can query a port with a command:

TCP socket test — query port 80

Queries on port 80 and 8080 always show a status 400. Querying the FTP port with an FTP command is also not conclusive:

TCP socket test — query port 21

I tried for some time some command injections on both parameters but was not successful. The port parameter only accepts numbers between 1 and 65535 and the cmd parameter filters all non-alphanumeric characters but spaces.

Let’s enumerate again the open ports, maybe some ports are only available locally. Knowing that a non-listening port raises a status code 500 when we query it, we can use the script below to get the open ports:

htb_dab_portbf.py

The output is:

OPEN => 21
OPEN => 22
OPEN => 80
OPEN => 8080
OPEN => 11211

We get a new port listed! The port 11211 is the port used by Memcached, which is a ‘general-purpose distributed memory caching system’. We could have guessed it based on the Status of cache engine: Online message on top of the page. The below query shows us the running version:

http://10.10.10.86:8080/socket?port=11211&cmd=version
VERSION 1.4.25 Ubuntu

Memcached has multiple known vulnerabilities, however, the filtering in place does allow us to try much. Let’s continue to query the service with the help of this cheat sheet document. Memcached organizes data by slabs, which are ‘categories of data of a given size range’. We can first list the available slabs with the command stats slabs:

TCP socket test — memcached — stats slabs

We see a bunch of stats and we can retrieve the active slab classes: 16 and 26. Then we retrieve the keys for each slabs with the command stats cachedump <slab class> <number of items to dump> (only the input command and output will be snown below):

stats cachedump 16 1000
ITEM stock [2807 b; 1549119016 s]
END
stats cachedump 26 1000
ITEM users [24625 b; 1549119137 s]
END

Promising…now that we have the keys, we can dump their value:

get stock
VALUE stock 0 2807
{"1": {"product": "Apples - Sliced / Wedge", "qty": 568}, "2": {"product": "Appetizer - Tarragon Chicken", "qty": 16}, "3": {"product": "Oil - Truffle, Black", "qty": 334},
[...]
get users
VALUE users 0 24625
{"quinton_dach": "17906b445a05dc42f78ae86a92a57bbd", "jackie.abbott": "c6ab361604c4691f78958d6289910d21",
"isidro": "e4a4c90483d2ef61de42af1f044087f3",
"roy": "afbde995441e19497fe0695e9c539266",
"colleen": "d3792794c3143f7e04fd57dc8b085cd4",
[...]
"admin": "2ac9cb7dc02b3c0083eb70898e549b63",
[...]
"demo": "fe01ce2a7fbac8fafaed7c982a04e229",
}
END

Note: You may retry to get those values a few times before they shows up, the time data get cached.

Ok, so we get a bunch of users and what seems to be their MD5 hashed password. The 2 accounts that we enumerated before, admin and demo are in the list. We were right, we can enumerate existing users from the login error message. The passwords of those 2 users are weak and can be easily cracked online:

admin:Password1
demo:demo

Now we can login on the web app running on port 80. There is no visible difference whether we login with the user admin or demo. We get the list of stock we retrieved before with Memcached:

Dab web server port 80 — admin login

After login in, we get a session cookie that looks like this:

session=eyJ1c2VybmFtZSI6ImFkbWluIn0.DzTQ1g.eFOuYcDLVgvMaSHK2WSp81GzKws

This looks like a JSON Web Token (JWT) but it’s not. However, the structure seems the same: 3 different parts, base64 encoded. Let’s decode each part:

$ echo “eyJ1c2VybmFtZSI6ImFkbWluIn0==” | base64 -D 
{“username”:”admin”}
$ echo "DzTQ1g==" | base64 -D | xxd -p
0f34d0d6
$ echo "eFOuYcDLVgvMaSHK2WSp81GzKws==" | base64 -D | xxd -p
7853ae61c0cb560bcc6921cad964a9f351b32b0b

Note: the base64 padding characters (equal) are deleted by the app. We need to add them to correctly decode the strings.

The first part is obvious and my first idea was that there is maybe an SQL injection to exploit. Especially after reading some comments in the source code of the page:

<!-- Debug… data tables were loaded from : MySQL DB -->and once the data are cached:<!-- Debug... data tables were loaded from : Cache -->

The second part seems like a number, in our case, it is equal to 255119574 in decimal. Further tests show that this value changes after each login and that 2 logins done at a 1-minute interval have a value difference of 60. Therefore, the second part is a timestamp in seconds.

The third part looks like the SHA1 hash. For sure this hash is a signature based on the previous 2 parts.

However, after many tries and some hours lost in trying to find out how to compute this hash and get a valid cookie, I found out this is a Flask session cookie and that the last part is an HMAC-SHA1 signature computed using the payload (part 1), the timestamp (part 2) and a secret.

Therefore, we can not do anything without the secret. Either there is another vulnerability to get it, or it’s a dead-end.

So, we are left with a list of users, the stock information and no idea what to do next. After a few hours of tries and other dead-ends, I remembered the possible OpenSSH users enumeration vulnerability and gave it a try. We can retrieve the list of all the existing users with the below script (make sure the users are cached before running it):

htb_dab_getusers.py

Then we use Metasploit to go through the list with the ssh_enumusers payload:

Metasploit — ssh_enumusers

And we get a hit!

Metasploit — ssh_enumusers hit

The genevieve user has as well a weak password: Princess1. Now we can SSH into the server and get the user flag:

gaining user access

[3] Local Reconnaissance & Enumeration

Now that we have a low-privilege access to the server we can start to work through elevating our privileges. Let’s first start with gathering some information. Some known privileges escalation enumeration scripts like LinEnum.sh or linuxprivchecker.py could be uploaded on the server and used, however, let’s keep them for later if necessary.

Let’s check if our user has some SUDO access:

$ sudo -l
[sudo] password for genevieve:
Matching Defaults entries for genevieve on dab:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User genevieve may run the following commands on dab:
(root) /usr/bin/try_harder

We can run the binary /usr/bin/try_harder with root privileges. When running it, we get what seems a root prompt but when we run a command, it fails:

$ sudo /usr/bin/try_harder 
root@dab:~# id
Segmentation fault
That would have been too easy! Try something else.

It’s a decoy as all the strings are hardcoded and printed:

$ ltrace /usr/bin/try_harder 
__libc_start_main(0x4006a6, 1, 0x7ffe70292248, 0x400720 <unfinished …>
printf(“root@dab:~# “) = 12
fgets(root@dab:~# id
“id\n”, 64, 0x7f06453b88e0) = 0x7ffe70292110
puts(“Segmentation fault”Segmentation fault
) = 19
sleep(3) = 0
puts(“That would have been too easy! T”…That would have been too easy! Try something else.
) = 51
+++ exited (status 0) +++

Let’s collect all the files owned by root (user or group) and that have the SUID or SGID bit set. If one of these bits is set, the program will run with the owner or group privileges respectively:

root SUID and SGID files

In red, we can see 3 weird binaries that we are not used to see on a Linux server with a SUID bit set and that will need further analysis. Especially the ldconfig, could be dangerous as it is used to “create the necessary links and cache to the most recent shared libraries found in the directories specified on the command line, in the file /etc/ld.so.conf, and in the trusted directories (/lib and /usr/lib)”.

[4] Privilege Escalation

Let’s analyze the binaries flagged above. When we run myexec we are asked to provide a password. A simple ltrace and we get it:

ltrace /usr/bin/myexec

When we use the right password we get a strange message:

/usr/bin/myexec

Using strace to check the system calls we see that the binary loads a shared library called libseclogin.so:

strace /usr/bin/myexec

This is not a common shared library and its name corresponds to the function seclogin() that the binary calls. We must find a way to create our own shared library called libseclogin.so and make myexec use it instead of the default one. This is something that would normally be achieved by settings the environment variable LD_PRELOAD to a directory where we can write, however, this does not work on SUID binaries for evident security reasons.

What is interesting though, is that we have as well root execution on ldconfig binary! This command uses the config file /etc/ld.so.conf to load and cache shared libraries, let’s see the config:

/etc/ld.so.conf

The config just says to include whatever config file is in /etc/ld.so.conf.d/. This folder has world-writable access, which is not the default. We can add our own config that points to a controlled folder containing our crafted shared lib. However, this is not really needed as there is already a weird test.conf that point to /tmp. We can simply put our lib there.

The shared library code will be very basic and will just spawn a shell. The compilation must be done on a Linux box.

htb_dab_libseclogin.c

We then upload our shared library on the /tmp folder with scp:

$ scp libseclogin.so genevieve@10.10.10.86:/tmp

We run ldconfig to rebuild its cache and we can see our shared library being linked. We then only need to run myexec binary to get a root shell and…Voilà!

gaining root access

[5] Conclusion

This was a tricky box with lots of enumeration and decoys left on purpose. Gaining user access was more tricky than the privilege escalation. I would rate this box as a medium difficulty challenge.

To make it short:

  • Do not expose dev environments publicly,
  • Secure your services with strong authentication schemes and password policies,
  • Patch your packages on a regular basis,
  • Review and monitor file permissions and access rights.

Do not hesitate to comment below if you found alternative ways to root this box. I would be interested to know your solutions.

This post is for educational and awareness purpose only. You are solely responsible for any actions and/or activities related to the material contained within this post. I will not be held responsible in the event any criminal charges be brought against any individuals misusing the information in this blog to break the law.

Cyber Security Professional and CTFer from Switzerland.