Secure Server
hackthebox
Task: Fix vulnerabilities in a PHP web application that was exploited via LFI + log poisoning. Solution: Replace include with readfile, add basename() and regex validation to prevent path traversal.
$ ls tags/ techniques/
Secure Server — HackTheBox
Description
Bobby made a secure server, but someone got in! How did they get in? Can you stop it from happening again?
This is a "secure coding" challenge where you need to identify and fix vulnerabilities in a PHP web application. The challenge provides:
- Access to the web application source code via SMB share
- An exploit script (exploit.py) showing how the attack works
- A checker service to verify fixes
Analysis
Vulnerability 1: Local File Inclusion (LFI) + Log Poisoning → RCE
The vulnerable code in our-projects.php:
<?php $project = "orion"; if (isset($_GET["project"])) { $project = strtolower($_GET["project"]); } ?> ... <p><?php include "../projects/" . $project; ?></p>
Problems identified:
includeexecutes PHP code from included files- No validation on
projectparameter allows path traversal (../) - Attacker can include any file on the system
Vulnerability 2: Path Traversal
No validation on the project parameter allowed ../ sequences to escape the intended directory and access arbitrary files like /var/log/nginx/access.log.
Attack Chain (from exploit.py)
from requests import get IP = '127.0.0.1' payload = 'whoami' php_payload = f"<?php system('{payload}') ?>" headers = { 'User-Agent': php_payload } # Step 1: Poison the log with PHP code in User-Agent get(f'http://{IP}/', headers=headers) # Step 2: Include the poisoned log via LFI r = get(f'http://{IP}/our-projects.php?project=../../../../var/log/nginx/access.log') print(r.text)
Attack flow:
- Send request with PHP payload in User-Agent header
- Nginx logs the malicious User-Agent to access.log
- Use LFI to include access.log
- PHP executes the injected code → RCE
Solution
Fix 1: Replace include with readfile
Changed from include (which executes PHP code) to readfile (which only outputs file contents as text):
// Before (vulnerable): <p><?php include "../projects/" . $project; ?></p> // After (fixed): <p><?php readfile("../projects/" . $project); ?></p>
Why this works: readfile() outputs file contents as raw text without parsing PHP tags, preventing code execution even if malicious content is included.
Fix 2: Input Validation with basename() and regex
Added strict validation to prevent path traversal:
<?php // default to Orion $project = "orion"; if (isset($_GET["project"])) { // Get basename first to strip any path components $requested = basename($_GET["project"]); // Convert to lowercase $requested = strtolower($requested); // Only allow alphanumeric characters, hyphens and underscores if (preg_match('/^[a-z0-9_-]+$/', $requested)) { $project = $requested; } } ?>
Defense layers:
basename()- strips directory components (../../../../etc/passwd→passwd)strtolower()- normalizes inputpreg_match('/^[a-z0-9_-]+$/')- whitelist only safe characters
Fix 3: XSS Protection (bonus)
Added htmlspecialchars for output to prevent reflected XSS:
<h1><?php echo htmlspecialchars(ucfirst($project)); ?></h1>
Complete Fixed Code
<?php // default to Orion $project = "orion"; $allProjects = ["orion", "ares", "ceres"]; if (isset($_GET["project"])) { // Get basename first to strip any path components $requested = basename($_GET["project"]); // Convert to lowercase $requested = strtolower($requested); // Only allow alphanumeric characters, hyphens and underscores if (preg_match('/^[a-z0-9_-]+$/', $requested)) { $project = $requested; } } ?> <!-- ... HTML ... --> <h1><?php echo htmlspecialchars(ucfirst($project)); ?></h1> <p><?php readfile("../projects/" . $project); ?></p>
Key Indicators
Use this technique (LFI + Log Poisoning) when:
- PHP
include/requirewith user input - No path validation (can use
../) - Access to web server logs (nginx/apache)
- User-Agent or other headers are logged
Defense (Best Practices)
- Never use
include/requirewith user input - usereadfile()orfile_get_contents()instead - Always validate and sanitize user input with allowlists (regex, basename)
- Defense in depth - multiple layers of validation
- Use
htmlspecialchars()for all output to prevent XSS
Tools
- Docker - for running the challenge environment
- smbclient - for accessing files via SMB share
- curl - for testing the exploit and fixes
- netcat - for interacting with the checker service
- Python3 - for automation (smb_edit.py)
Notes
The challenge required maintaining functionality (dynamic file reading) while removing the security vulnerabilities. Simply restricting to a whitelist of projects wasn't acceptable - the checker tested with random project names, so the fix needed to allow arbitrary (but safe) filenames.
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar
Similar writeups
- [web][Pro]RCE— web-kids20
- [web][Pro]Docker2— web-kids20
- [web][Pro]local— web-kids20
- [web][Pro]Waf— web-kids20
- [web][Pro]SWE Intern at Girly Pop Inc — Writeup— scarlet