1.HttpListener的一些基本情况
这玩意虽然叫Listener,但和JAVA EE那边的同名机制不一样。可以把这玩意理解为python的http.server,算是一种比较简单和轻量级的.NET WEB服务。是的,打这个内存马相当于在目标服务器上开了一个额外的WEB服务,但是它可以支持端口复用,我们可以复用目标的正常的WEB服务的端口(看起来就像是内存马,实际上运行在不同的服务),依靠这些性质,就可以去实现一个内存马。那么我们要怎么使用HttpListener呢?微软文档给出的使用案例如下:
看起来有点乱,不过其实主要是三步走:
①实例化一个HttpListener类的对象
②调用listener.Prefixes.Add()添加HttpListener的URL,然后调用Start()方法开启Listener服务。比如上面Add()传入的s,如果是
http://127.0.0.1:8080/index/
那么就会在这个地址开一个新的HttpListener服务,可以端口复用
③Start()之后就是你的Listener正式的逻辑,可以拿到HttpListenerContext,并可以进一步拿到HttpListenerRequest、HttpListenerResponse类的对象,可以获取一些请求信息并设置响应信息
这里有个很重要的注意事项就是HttpListenerContext、HttpListenerRequest、HttpListenerResponse。很容易让人联想到普通.NET WEB应用里的HttpContext、Request、Response等概念,然而他们完全不是同一个东西,不过好在HttpListenerRequest实际上也足够获取到很多请求信息,参考:
https://learn.microsoft.com/zh-cn/dotnet/api/system.net.httplistenerrequest.querystring?view=net-8.0#system-net-httplistenerrequest-querystring
比如可以通过request.QueryString.Get("cmd");获取cmd参数
2.使用Listener机制实现内存马
这里还是使用下面这个文章的代码:
https://exp10it.io/2024/02/asp.net-%E5%86%85%E5%AD%98%E9%A9%AC/#httplistener
写一个方法用于启动HttpListener。这个方法接受一个URL,作为后续启动HttpListener时的监听地址,然后后面通过HttpListenerRequest拿到cmd传参
再后续就是命令执行的实现点,并且通过response返回响应信息。然后启动这个Listener的代码也很有趣
为什么要加上:
Thread t = new Thread(StartHttpListener);
t.Start(url);
这其实是为了将监听器放在一个单独的线程中,可以让主线程继续处理其他请求或操作,而不需要等待监听器的运行完成。如果没有这段代码,HTTP监听器将会在主线程中运行,导致整个页面的加载和其他操作被阻塞,用户体验会受到影响。
我们来运行一下试试,访问上述ASPX文件进行注入,然后访问之
可以成功RCE。不过这玩意还有些很有趣的性质,还记得我们前面说的,这玩意其实相当于自己开了一个简单的WEB服务,只不过它可以端口复用,观感上特别像内存马。
因此,这玩意实际上不必非得从WEB打进去,我们可以给它打包成exe版本的,或者是dll版本的,比如我下面的实现
运行这个exe版本的注入器,指定本地的44393这个web端口
而且其实甚至不需要依赖现有的WEB服务,前面说过这玩意相当于另起了WEB服务,比如我本地的44332端口啥服务都没开
还是可以起到webshell的效果
这个就非常妙了,比如一些比较极端的MSSQL RCE场景,可以通过落地上述exe文件,或者把上面的代码改写成CLR DLL版本,然后直接进行一个执行,去强行打上一个“内存马”,很多操作会变得方便许多。这也是SqlmapXplus这款工具通过MSSQL注入内存马的原理。顺带一提,上面那个exe编译出来免杀效果还挺好的,免杀带手子随便操作两下应该很容易做到全绿
不过上述代码也不是没有缺点,对80端口的复用必须要system权限,而且我们前面多次提到,这玩意的HttpListenerRequest、HttpListenerResponse和普通的Request、Response不是一个东西,也就是说想用这玩意去打一个哥斯拉或者其它webshell的内存马,还有很多工作要等着我们做。
3.封装一个HttpContext
有一种办法是根据已有信息封装一个HttpContext出来,HttpContext的构造方法需要我们提供一个HttpRequest和HttpResponse类的对象,我们可以研究一下根据已有的信息能否做出这两个类的对象
首先看看HttpRequest,有一个构造方法是这样的:
这里有两个关键参数,url和queryString,url就是访问的url,没啥好说的,queryString就是我们所有的传参。只要提供这两个信息就可以靠上面的构造方法做一个HttpRequest对象,而这些信息靠HttpListenerRequest也可以获取到,HttpResponse也是同理,因此可以看到下面这样的实现:
https://github.com/A-D-Team/SharpMemshell/blob/main/HttpListener/memshell.cs
这样就可以借助HttpListener的信息去做一个HttpRequest和HttpResponse出来,后续调用相关的属性和方法也比较方便,此外这里还实现了一个parse_post()方法用于获取post传参:
靠上述这些代码为基础可以把哥斯拉webshell改造成HttpListener的版本,不过上述操作做出来的HttpRequest其实还是有点问题的,不能适配蚁剑,还有优化的空间,可以参考yzddMr6师傅的文章
https://yzddmr6.com/posts/asp-net-memory-shell-httplistener/#%E5%85%B3%E4%BA%8Ehttplistener
4.总结
HttpListener内存马算是一种特殊的内存马,相当于重新开了个web服务,因此注入这个内存马的方法也比较多,很灵活,不过在80端口注入这个内存马需要system权限。
5.参考
https://yzddmr6.com/posts/asp-net-memory-shell-httplistener/#%E6%B5%8B%E8%AF%95
https://exp10it.io/2024/02/asp.net-%E5%86%85%E5%AD%98%E9%A9%AC/#httplistener