narancs's blog

TryHackMe – UltraTech walkthrough


TryHackMe UltraTech logo

In this post I will show how I solved the UltraTech room on TryHackMe. This is a medium difficulty room, but there are extra questions that guide us in the right direction. We need to exploit a web API written in Node.js, then escalate privileges to read the root user’s private SSH key.


Nmap scan results:

4 ports are open:

  • FTP is running on port 21
  • SSH is running on port 22
  • Node.js Express framework is running on port 8081
  • Apache web server is running on non-standard port 31331. From the http header we can assume it’s an Ubuntu server.

From the scan results we can answer the first 4 questions of Task 2.

The 5th question is: “The software using the port 8080 is a REST api, how many of its routes are used by the web application?” (I guess there is a typo in the question and it should be port 8081.)

I enumerated the available routes with gobuster:

					gobuster dir --url -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 32

It found 2 routes: /auth and /ping. The next task is exploitation.


/auth route

I checked both route. First I opened in browser, that showed: You must specify a login and a password

I assumed it is expecting GET parameters in the URL so I tested it with I received a different error: Invalid credentials

This means that the /auth endpoint is indeed expecting GET parameters (named ‘login’ and ‘password’). However without knowing neither the login name nor the password it would be hard to brute-force the correct credentials.

/ping route

Next I checked the /ping route.

From the error message I assumed that there is a JavaScript code on the back-end that tries to use the string replace() method on a variable, but the value of this variable is undefined. I knew from this that there should be a GET parameter in the URL that we need to provide, that is why it is currently “undefined”. But I did not know what the parameter name was.

After this I started checking the web server that is running on port 31331. I enumerated directories with gobuster:

					gobuster dir --url -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 32

I found 4 results: /images, /css, /js and /javascript. The /javascript directory was forbidden, so I looked into /js. It contained 3 files: api.js, app.js and app.min.js. While checking these files I found something interesting in /js/api.js:

					const url = `http://${getAPIURL()}/ping?ip=${window.location.hostname}`
This line in the code showed how the /ping API is used. It receives a hostname (or IP address) in a GET parameter named ‘ip‘. I tested it with the IP of the target VM ( and got the following result:
					PING ( 56(84) bytes of data.
64 bytes from icmp_seq=1 ttl=64 time=0.018 ms
--- ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.018/0.018/0.018/0.000 ms

So the server runs the ping command on the provided IP address and returns the results. At this point I tried to find a way to exploit this API.

I imagined that the back-end code would save the value of the ip parameter into a variable, and use that as a command line argument for the ping command. After saving the ip in a variable, it also runs the replace() method, probably to filter out special characters that could be used to forge a malicious payload.

I was able to verify this by testing some easy options, e.g.:


I received an error from the ping command:

					ping: Name or service not known

So ‘;‘ is replaced with empty string and is passed to the ping command as an argument. Then I tried to chain commands together with &&, but the ping command was executed normally, and I didn’t get any additional output.

Then I though what if I use command substitution to pass the output of arbitrary commands to ping, then I would see the result in the error message. First I tried: /ping?ip=$(whoami), but I got an error: /bin/sh: 1: Syntax error: "(" unexpected.

Finding password of r00t

However there is another way to do command substitution, using ``.

Error message:

					ping: www: Temporary failure in name resolution

It worked! www is the output of the whoami command and ping returns it to us in the error message. Let’s try it with the ls command.

					ping: utech.db.sqlite: Name or service not known 

That is the answer for the next question!

At this point we can run any command on the target that has some output and we get the result in the error message. Let’s output the contents of the file we just found.

`cat utech.db.sqlite`

The output:

					ping: ) ���(Mr00tf357a0c52799563c7c7b76c1e7543a32)Madmin0d0ea5111e3c1def594c1684e3b9be84: Parameter string not correctly encoded 

We found 2 username and password hash combinations.

  • r00t : f357a0c52799563c7c7b76c1e7543a32
  • admin : 0d0ea5111e3c1def594c1684e3b9be84

Crack the password of the user r00t:

					hashcat -a 0 -m 0 f357a0c52799563c7c7b76c1e7543a32 /usr/share/wordlists/rockyou.txt --quiet

Next step is getting the SSH key of the root user.

Privilege escalation

We can SSH to the target with the r00t user and password cracked in the previous task. To answer the last question we need to get access to the real root user.

If we check the groups of the user, we see it is part of the docker group.

					r00t@ultratech-prod:~$ id
uid=1001(r00t) gid=1001(r00t) groups=1001(r00t),116(docker)

Adding a user to the docker group is basically giving them root access to the filesystem. I found a short and simple explanation at

Access to run docker commands on a host is access to root on that host. This is the design of the tool since the functionality to mount filesystems and isolate an application requires root capabilities on linux. The security vulnerability here is any sysadmin that grants access to users to run docker commands that they wouldn't otherwise trust with root access on that host. Adding users to the docker group should therefore be done with care.

We can check the available docker images on the host and see that the bash image is available:

					r00t@ultratech-prod:~$ docker images 
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
bash                latest              495d6437fc1e        3 years ago         15.8MB

We can use that image to start a container and mount the entire host filesystem into the container at /mnt with the following command:

					docker run -it -v /:/mnt bash

The private SSH key of the root user is stored in /root/.ssh, so inside the container we can access it in /mnt/root/.ssh:


In the UltraTech room we could see how a simple API could give a lot of information from the target. We were able to exploit the ping command to give us valuable information through error messages. With this information we were able to get initial access to the target.

After logging in via SSH using the acquired credentials we found another critical vulnerability: r00t user was added to the docker group, granting read access to the entire filesystem. We were able to read the private SSH key of the real root user by starting a container and mounting the host filesystem into the container.

Table of Contents

Notify of
Inline Feedbacks
View all comments

Related posts

Would love your thoughts, please comment.x