webfreemedium

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/
lfi_to_rcelog_poisoninginput_validationbasename_sanitization

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:

  1. include executes PHP code from included files
  2. No validation on project parameter allows path traversal (../)
  3. 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:

  1. Send request with PHP payload in User-Agent header
  2. Nginx logs the malicious User-Agent to access.log
  3. Use LFI to include access.log
  4. 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:

  1. basename() - strips directory components (../../../../etc/passwdpasswd)
  2. strtolower() - normalizes input
  3. preg_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/require with user input
  • No path validation (can use ../)
  • Access to web server logs (nginx/apache)
  • User-Agent or other headers are logged

Defense (Best Practices)

  1. Never use include/require with user input - use readfile() or file_get_contents() instead
  2. Always validate and sanitize user input with allowlists (regex, basename)
  3. Defense in depth - multiple layers of validation
  4. 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