杂谈Java内存Webshell的攻与防——比较全,filter内存马可直接watch方法返回值来获取已经注册的Filter,然后dump出来用针对字节码的检测
杂谈Java内存Webshell的攻与防
这篇文章主要以Tomcat为例子记录了一些关于Java内存Webshell利用与检测以及相关的思考。
内存Webshell的利用方式
现在的内存Websell的利用方式个人感觉可以分为以下三种:
1. 基于Servlet规范的利用,动态注册Servlet规范中的组件,包括Servlet,Filter,Listener,这部分的公开文章比较多,比如:
2. 基于特定框架的利用,框架一般对于Servlet又进行了一层封装,动态注册框架的路由,文章:
3. 基于javaagent修改Servlet处理流程中的字节码,工具:
前两种利用方式的利用场景要求为可以执行任意Java代码,比较常见的场景包括:反序列化,JNDI注入等场景,不过现在公开的工具,在一些场景利用起来依然不太方便,典型的比如shiro反序列化漏洞不出网的情况下利用后想要代理进内网的场景,所以能够利用反序列化直接注入一个可以使用一些功能比较强大的webshell管理工具(典型的比如冰蝎)的内存webshell 会比较方便后续的利用,由于各个师傅已经给出了比较详细的思路,所以我就做了一下整合,方便后续的测试。几个需要修改的地方:
冰蝎的客户端在很多景中,并没有jsp相关的依赖,需要把pageContext换掉。
摆脱Tomcat header的限制,需要缩小payload的体积,同时还需要处理一下冰蝎服务端原有的内部类。
工具地址:
内存Webshell的检测
如何检测内存Webshell,之前有师傅提出了利用VisualVM监控mbean来检测,用上面的工具注入内存webshell之后确实可以在Filter中看到注入的Shell
这个的检测原理主要是在注册类似Filter的时候会触发registerJMX的操作来注册mbean,org.apache.catalina.core.ApplicationFilterConfig#initFilter
不过mbean的注册只是为了方便资源的管理,并不影响功能,所以攻击者植入内存Webshell之后,完全可以通过执行Java代码来卸载掉这个mbean来隐藏自己。
利用冰蝎运行自定义代码功能卸载mbean的demo:
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.util.Set;
public class UnRegister {
static {
try{
Class registryClass = Class.forName("org.apache.tomcat.util.modeler.Registry");
MBeanServer mBeanServer = (MBeanServer) registryClass.getMethod("getMBeanServer").invoke( registryClass.getMethod("getRegistry", Object.class, Object.class).invoke(null,null,null));
Set<ObjectName> objectNameSet = null;
objectNameSet = mBeanServer.queryNames(null, null);
for (ObjectName objectName : objectNameSet) {
if ("Filter".equals(objectName.getKeyProperty("j2eeType"))) {
Object filterName = mBeanServer.getAttribute(objectName, "filterName");
if ("litchi".equals((String) filterName)) {
mBeanServer.unregisterMBean(objectName);
}
}
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
1、利用Java Instrument检测
对于内存马的检测,最后还是落在JVM的内存层面上的,所以想要比较精准的检测内存马的话,可以考虑利用Java Instrument技术,简单的思路比如:
对于敏感类(比如实现了javax.servlet.Filter接口的类)利用retransformClasses方法进行retransform,编写ClassFileTransformer实现类将这些敏感类加载的class dump出来,接着就可以使用反编译工具转换成源代码判断是否是Webshell,或者有一些webshell检测工具支持字节码的检测,比如:WebshellChop 就不用反编译可以进行批量的检测。
当然如果不想自己编写,在这里推荐一个工具可以比较方便的检测:Alibaba开源的Java诊断工具arthas,arthas也是利用了Java Instrument技术,能比较好的解决一些线上应用难调试,难监控的问题,功能比较强大,支持命令行交互模式,关于arthas的具体细节可以查看他的文档,我们先来直接看看检测的demo。
2、利用arthas检测Filter类型
由于arthas可以直接观察方法调用的情况,所以可以很直接的获取执行流程中准确的对象,以检测Filter为例子:在处理Filter的过程中ApplicationFilterChain来自于createFilterChain方法,所以我们可以直接watch这个方法的返回值来获取已经注册的Filter,启动arthas attach到指定进程上之后执行如下表达式
watch org.apache.catalina.core.ApplicationFilterFactory createFilterChain 'returnObj.filters.{?#this!=null}.{filterClass}'
访问url触发
接着利用jad命令观察可疑的filterClass的源代码:
jad ysoserial.payloads.TomcatShell
无论是从classLoader的信息,还是doFilter方法的实现都可以进行判断
3、结合牧云进行批量检测
如果攻击者隐藏的够好,要判断是否可疑还是不太容易的,这时候可以构造正则表达式一次性把所有的Filter导出来,直接扔到扫描器中进行批量检测。不过目前arthas的交互shell对于管道以及字符串操作的支持还不是特别好,可以先在外面的shell中先进行字符串处理
把dump出来的有class的文件夹直接丢到长亭的主机防护产品:牧云中就可以直接支持检测,牧云支持直接的针对class的字节码类型的检测
由于arthas也支持批处理的调用,这部分的处理流程也可以写成一个定时任务来进行定时的自动化检测,这样检测就比较方便。
4、利用arthas检测基于框架的类型
以
为例子,可以直接观察DispatcherServlet匹配handler的过程,把所有的尝试匹配都找出来就可以找到所有的RequestMapping。
watch org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry getMappings "returnObj"
#访问随意一个url触发
拿到所有的RequestMapping对应的HandlerMethod之后就可以去检测对应的class了
5、利用arthas检测javaagent类型
以memShell的检测为例子,这部分的检测效果不是特别好
原因如下:
1. 由于这种类型采用了增强字节码的机制来改变执行的流程,在处理请求的流程中,可以增强字节码的地方很多,检测起来比较无从下手
2. 由于在dump字节码的时候都是利用了retransformClasses,这个执行流程会造成互相干扰
以memshell的检测为例子,jad反编译memshell修改的ApplicationFilterChain可以看到并没有如预期一样打印出被修改过的代码,原因可以看
memshell中的ClassFileTransformer第二次执行的时候就会出现异常导致没有成功把ApplicationFilterChain修改掉,所以jad命令看到的也就是没有修改的字节码了,不过这也相当于已经把这个内存shell给删除了。
不过我们依然可以从其他特征来入手检测,比如考虑是不是添加了恶意的shutdownhook
ognl "@java.lang.ApplicationShutdownHooks@hooks.keySet().toArray().{getClass()}.{getName()}"
6、总结
其实利用类似的agent类型的思路可以做到比较精准的发现类似的内存Webshell,但是前提是你得知道内存Webshell大致隐藏在哪,这也是比较考验防守方的地方,需要对内存Webshell的注入场景有比较好的覆盖 ,将其转化为文件之后就可以利用支持字节码的检测工具批量进行定时自动化检测。
拓展:关于arthas思考
前几天在先知社区看到了关于openRasp的类加载机制的分析
学习了一下关于OpenRasp的类加载机制,arthas作为同样是利用Java Instrument的类型的产品做到了比较好的类隔离机制来减少对于线上应用的侵入这里就arthas的类加载机制来进行一个简单的讨论。
首先为了做到类隔离,arthas-core是由agent利用自定义的类加载器加载的,来做到与业务方进行类隔离
接着我们把arthas的全局dump以及unsafe选项打开,来看看watch一个由启动类加载器加载的类的某个方法时arthas是如何进行增强的。
将dump下来的class进行一下反编译,这里以java.lang.ApplicationShutdownHooks的add方法为例子:
可以看到对于字节码的增强,是直接调用的SpyAPI的方法,而这个SpyAPI在启动Agent的时候就已经把他加入到了BootstrapClassLoader中,所以可以顺利调用他的atEnter方法,但是对于不同的指令来说,这个方法都需要具体的实现,最后实现肯定需要调用core里面的方法,那么是如何实现的呢?来看看SpyAPI类的实现
这个AbstractSpy的实例是什么呢?其实在使用的时候,这个实例被设置为SpyImpl的实例,而这个SpyImple类是由ArthasClassLoader加载的,这样就做到了可以在任意地方调用自定义类加载器中的方法。
本人水平有限,如果师傅发现文章中的疏漏错误,欢迎指出来可以一起探讨~