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.phpand/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=6My 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:
- Enumeration revealed the API and admin panel
- IDOR leaked the administrator's identity
- Weak password reset allowed account takeover
- Admin access exposed the upload function
- Extension bypass enabled PHP execution
- 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
Post a Comment