前情提要
谈起WebShell相信安全爱好者多少都有点来劲儿,无论是小马、大马还是一句话木马都曾各刑其道,给我们带来过快乐,但这些种在服务端的马都有一个共性:有文件落地。
有文件落地就意味着更容易触发告警,更容易被安全设备捕获特征并查杀,随着近年来企业红蓝对抗和HW的开展,这种有文件落地的WebShell可以说已经日薄西山,气数将尽。与此同时,企业级应用仍然是Java语言和框架的江山,笔者在梳理内存WebShell的“历史演进”过程中,发现互联网上最早讨论Java内存WebShell技术的是n1nty大佬(《Tomcat 源代码调试笔记 – 看不见的 Shell》—2017-06-23),而且这种方法就是后文讨论的JSP Filter型内存马的实现,但当时并没有引起太多讨论;后来rebeyond大佬又提到一种Java Agent内存马(《利用“进程注入”实现无文件复活 WebShell》—2018-05-30),基于instrument机制,在利用过程中需要上传两个jar文件:inject.jar和agent.jar然后执行,这种内存WebShell的利用条件相对比较苛刻(上传、命令执行),往往用于拿到WebShell后进一步的隐藏与维持访问。
时间匆匆过去两年,LandGrey师傅在2020-02-17发表《基于内存 Webshell 的无文件攻击技术研究》,面向Java常用Web框架SpringMVC,注册Controller,实现注入内存WebShell。
随后关于内存WebShell的讨论就开展起来,threedr3am师傅提出《基于tomcat的内存 Webshell 无文件攻击技术》—2020-03-18,借用kingkk师傅反射修改控制变量的方式,通过lastServicedRequest和lastServicedResponse获取到上下文对象,然后在Tomcat里动态注册Filter,整个过程需要执行两步。值得注意的是该方法对Shiro反序列化漏洞利用无效,因为反射修改ApplicationDispatcher.WRAP_SAME_OBJECT为true之前,Shiro服务已经执行完所有Filter了。
Litch1师傅在《基于全局储存的新思路 | Tomcat的一种通用回显方法研究》—2020-03-20里提出通过全局储存的思路,通过 Thread.currentThread().getContextClassLoader()来获取 Tomcat 上下文,从而实现Tomcat的通用回显,该方法对Shiro反序列化漏洞有效,但是因为代码结构不同,导致该方法在Tomcat7下无法实现回显。
之后,l1nk3r师傅发表《基于Tomcat无文件Webshell研究》—2020-03-29,对上面几篇文章的思路做了汇总,在Servlet内存马里提出了addServletMapping的添加方式,代码简洁优雅,令人耳目一新。
言归正传,在汇总了上面各位师傅的研究和互联网其他师傅基于上述研究实现的代码demo之后,千头万绪,思路纷乱,于是笔者决定进行一番梳理并将Tomcat Servlet-Api内存马的原理分别应用到JSP文件和反序列化漏洞利用场景中,阐述其具体实现,告别demo,以贴合实战的Cmd回显内存马以及冰蝎内存马作为目的,分别实现Tomcat下Filter、Servlet和Listener的内存WebShell。
根据Tomcat版本做了适配,具体实现代码见github:
https://github.com/ce-automne/TomcatMemShell
当然,既然是Tomcat内存WebShell,就需要先从Tomcat开始说起。
Tomcat简介
Tomcat本质上是一种Servlet容器,其顶层架构图如下:
Tomcat中的Server代表服务器,一个Server包含至少一个Service,而Service主要包含Connector和Container,可以有多个Connector,但仅有一个Container。Connector用于接受请求并将请求封装成Request和Response,然后交给Container进行处理。Container用于封装和管理Servlet以及具体处理Request请求。重点看一下Container的内部结构,如下图。
Engine作为最顶层的容器组件,其下可以包含多个Host。对应组件的实现类如下:
Engine 实现类:org.apache.catalina.core.StandardEngine
Host 实现类:org.apache.catalina.core.StandardHost
Context 实现类:org.apache.catalina.core.StandardContext
Wrapper 实现类:org.apache.catalina.core.StandardWrapper
从上可以看到每个Wrapper封装着一个Servlet,Servlet 是服务器的 Java 应用程序,用于处理HTTP请求并做出相应的响应。Filter会在HTTP请求到达Servlet之前做一些过滤操作。另外一个接下来要讨论的对象就是Listener,Listener用于监听某些特定动作的监听器。当特定动作发生时,监听该动作的监听器就会自动调用对应的方法。
Servlet、Filter和Listener的执行优先级为:
Listener > Filter > Servlet
为了避免内容冗余,在下文的JSP内存WebShell中将会着重阐述注入方式,并以Cmd回显内存马为例;而在反序列化内存WebShell部分,将会着重说明注入冰蝎内存马的方法。
JSP内存WebShell
应用场景往往是攻击者上传或写入JSP文件让服务端解析,然后注入内存WebShell。这严格意义上来说不能算是内存WebShell,因为在Tomcat编译jsp文件的时候,会在Tomcat目录下有文件落地,如下图所示。当然,如果删掉下面3个文件,WebShell还是有效的,除非重启Tomcat容器,否则会一直驻留在内存里。
Filter型内存马
在JSP中获取上下文的ServletContext很方便,形如下面的代码
ServletContext servletContext = request.getSession().getServletContext();
接着再通过ServletContext就可以获取standardContext。
这里注入Filter型内存马的方法需要先说明如下3个对象的定义:
-
filterDefs存放了filter的定义,比如名称跟对应的类
-
filterConfigs除了存放了filterDef还保存了当时的Context
-
FilterMaps则对应了web.xml中配置的<filter-mapping>
要实现Filter型内存马,需要经过如下步骤:
1.创建一个恶意Filter,这里的doFilter方法实现的是一个Cmd命令执行回显。
2.创建一个FilterDef并对filter进行封装,定义其名称和类名
3.创建一个FilterMap并对filter进行封装,定义其名称和URLPattern
4.将filter添加到FilterConfig中
最后需要注意的是Tomcat7和Tomcat8/9/10的FilterDef、FilterMap包名差异。
Tomcat 7:
org.apache.catalina.deploy.FilterDef
org.apache.catalina.deploy.FilterMap
Tomcat 8/9/10:
org.apache.tomcat.util.descriptor.web.FilterDef
org.apache.tomcat.util.descriptor.web.FilterMap
Servlet型内存马
通过Tomcat的架构分析,我们知道“每个Wrapper封装着一个Servlet”,因为在JSP里获取StandardContext的方法是一致的,所以直奔主题。
要实现Servlet型内存马,需要经过如下步骤:
1.创建一个恶意Servlet,在service方法里写入恶意代码。
2.创建新的Wrapper对象,写入Servlet名称和类名。
3.将Wrapper对象添加到StandardContext的Children中,并且绑定URL。
Tomcat 7使用addServletMapping方法
Tomcat 8/9/10使用addServletMappingDecoded方法
Listener型内存马
当前业界讨论比较多的还是Filter型内存马,但是实际上Listener型内存马的优先级最高,且代码更简洁优雅。
比较好用的是ServletRequestListener,因为可以拿到每次请求的ServletRequestEvent,并通过其中的getServletRequest()函数拿到本次请求的request对象,从而拿到请求和响应。
实现Listener型内存马的逻辑很简单
1.创建一个恶意的Listener,并在requestInitialized方法里写入恶意代码。
2.通过addApplicationEventListener方法添加恶意Listener
没错,就是这么简单粗暴,即可实现一个Listener内存马。
反序列化内存WebShell
鉴于反弹shell能够轻易地被hids等设备检测到,可以尝试打入内存马取得WebShell权限再做进一步横移。
通过反序列化漏洞注入内存WebShell,实现真正的无文件落地WebShell。
上面提到的Cmd回显内存马仅作为注入成功的证明,但在实际利用中,这种Get传参无加密的命令执行流量极易被安全设备捕获。接下来以冰蝎客户端为例,阐述利用反序列化漏洞注入冰蝎内存马需要注意什么。
获取StandardContext的方式
考虑到代码的可用性和优雅性,这里使用了Litch1师傅在《基于全局储存的新思路 | Tomcat的一种通用回显方法研究》中获取StandardContext的方法。
在Tomcat 8/9/10版本中,通过Thread.currentThread().getContextClassLoader()的方式来获取 Tomcat 上下文。
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
获取到StandardContext之后,与JSP注入代码不同的地方是在java代码里需要使用反射机制,以注入Listener型内存马为例,如下代码所示。
LRain servletRequestListener = new LRain();
Method addlistener = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredMethod("addApplicationEventListener", Object.class);
addlistener.invoke(standardContext,servletRequestListener);
注入冰蝎服务端代码
上文提到恶意代码都是写在对应的恶意Filter、Servlet或者Listener中,这里以Listener型内存马为例,代码结构如下:
重点看一下怎么注入冰蝎服务端代码,首先看一下冰蝎在github里提供的jsp一句话木马。
可以看到定义了一个继承ClassLoader的U类,用于执行defineClass方法。这也是冰蝎实现动态解析二进制class文件的核心方法。
我将核心代码封装到RushThere函数里,并把上面提到的U类通过字节码的形式进行调用,这样能够解决一些特殊利用场景下的问题,比如Shiro漏洞在使用CC利用链的时候会要求extends AbstractTranslet,这样就会违背java不支持多重继承的机制。通过字节码定义后再通过反射来做后续的编码加密等操作。
另一点需要注意的就是确保上下文的类是否匹配到java.lang.ClassLoader,因为自定义的RushThere方法里需要调用defineClass方法联动冰蝎客户端,只有匹配到java.lang.ClassLoader才能正常执行。我这里使用了经典的if-else大法0.0。
解决了这些问题之后,就可以通过反序列化漏洞注入Servlet-Api内存马。
注入效果
1.JSP内存WebShell以Listener型为例,针对Tomcat环境,上传jsp文件并访问
返回>@<说明注入成功。
传参执行命令
2.反序列化内存WebShell以fastjson反序列化漏洞为例,将Filter型冰蝎内存马的class文件通过jndi远程加载,成功实现注入。
使用冰蝎客户端成功连接
内存马检测
Java内存马的检测目前主要有两种方法:1)基于反射的检测 2)基于instrument机制的检测
对于Tomcat Servlet-Api型内存马基于反射就可以实现检测了,具体实现如c0ny1师傅的java-memshell-scanner项目(https://github.com/c0ny1/java-memshell-scanner ),通过jsp脚本实现扫描和查杀。当前版本只支持对Filter和Servlet型内存马的检测与查杀。
对于不方便上传jsp脚本的场景,可以基于instrument机制进行检测,相关实现如LandGrey师傅的copagent项目(https://github.com/LandGrey/copagent ),支持对Filter/Servlet/Listener型内存马的检测,并按照风险等级排序。
下图为该项目的Agent.java查找实现servlet特殊接口的目标类的代码。
将cop.jar上传到目标环境上并运行,它将会自动attach jvm进程并扫描,如果在windows环境attach失败则需要通过-p参数手动指定进程。以fastjson环境注入的Listener型内存马为例,查看扫描结果如下
进一步查看反编译得到的ILRain.java文件,可以看到该java文件里与冰蝎进行了联动。
”
参考链接
1.利用“进程注入”实现无文件复活 WebShell
https://www.freebuf.com/articles/web/172753.html
2.基于内存 Webshell 的无文件攻击技术研究
https://www.anquanke.com/post/id/198886
3. Tomcat中一种半通用回显方法
https://xz.aliyun.com/t/7348
4.基于tomcat的内存 Webshell 无文件攻击技术
https://xz.aliyun.com/t/7388
5.基于全局储存的新思路 | Tomcat的一种通用回显方法研究
https://mp.weixin.qq.com/s?__biz=MzIwNDA2NDk5OQ==&mid=2651374294&idx=3&sn=82d050ca7268bdb7bcf7ff7ff293d7b3
6. 基于Tomcat无文件Webshell研究
https://mp.weixin.qq.com/s?__biz=MzI0NzEwOTM0MA==&mid=2652474966&idx=1&sn=1c75686865f7348a6b528b42789aeec8&scene=21#wechat_redirect
7. JSP Webshell那些事 — 攻击篇(下)
https://mp.weixin.qq.com/s/YhiOHWnqXVqvLNH7XSxC9w
8. 利用动态二进制加密实现新型一句话木马之Java篇
https://xz.aliyun.com/t/2744
9. Tomcat 源代码调试笔记 – 看不见的 Shell
https://mp.weixin.qq.com/s/x4pxmeqC1DvRi9AdxZ-0Lw
10. 四张图带你了解Tomcat系统架构
https://blog.csdn.net/xlgen157387/article/details/79006434
11. Filter/Servlet型内存马的扫描抓捕与查杀
https://gv7.me/articles/2020/filter-servlet-type-memshell-scan-capture-and-kill/
原文始发于微信公众号(讯飞安全):分享|Tomcat Servlet-Api内存马总结及代码实现