之前的文章基本上都是围绕在tomcat或者spring环境下如何解决下面几个问题:
如何获取reqest / response
域,也即是如何回显
如何利用这些中间件的特性进行内存马的构造,也即是动态创建路由
如何在反序列化漏洞的过程中注入内存马进行不出网等等的利用
总的来说,主要是从“攻”的角度进行内存马的学习。
接下来在继续抽空学习和挖掘其他的内存马的过程中,也开始从“防”的角度学习内存马的查杀。
前面几节主要是通过已知的一些查杀工具学习学习别人的思路。
第一个学习的项目是c0ny1
师傅在几年前开源的项目了
https://github.com/c0ny1/java-memshell-scanner
这个项目的主要思路是获取Tomcat下的所有的存在的Servlet
/Filter
/Listener
分别获取对应的各种信息,主要是通过判断对应的Servlet / Filter / Listener
类是否在服务器端存在有对应的class文件,如果在classpath下没有对应的类文件则判断其可能为内存马
在这个tool中,内置了两个功能
dump
下对应的class文件
kill
掉对应的servlet / fitler / listener
接下来我们详细看看,是怎么进行判断的
首先看看两个方法
getChildren
这里方法的作用主要是通过从request
这个HttpServletRequest
对象中获取StandardContext
类对象,这个类是"上下文接口的标准实现。每个子容器都必须是一个 Wrapper 实现,以处理指向特定 servlet 的请求。", 之后获取这个对象中的children
属性,这个属性也是其父类ContainerBase
的值
也就是基于这个Context
的一些子容器
也就是一些servlet -> StandardWrapper
的映射
而另一个方法getServletMaps
类似的是从StandardContext
对象中获取其属性servletMappings
,也即是一些router -> servletName
的映射
之后的主要步骤也即是
通过遍历存在的所有的所有路由
分别获取servletPath / servletName / standardWrapper
这些信息之后
通过Class.forName
进行类的加载
获取对应servlet类的classloader
通过对应的classloader调用getResource方法获取源文件
如果没有获取到文件就认为其可能为内存马
结合前面Tomcat Servlet的动态注册一个servlet的过程
Servlet存马的创建流程
创建恶意Servlet
用Wrapper对其进行封装
添加封装后的恶意Wrapper到StandardContext的children当中
添加ServletMapping将访问的URL和Servlet进行绑定
将恶意Servlet的Wrapper添加进入了children
属性中
将路由的映射添加进入了servletMappings
属性中
这里的检测思路就是在我们注册内存马的必要步骤中进行处理
针对于Filter型内存马的检测,采取的是和Servlet型类似的检测方法,也即是获取所有存在的Filter,之后判断资源文件是否存在
类似的,简单看一下两个关键的方法
getFilterConfig
这个方法同样是获取了StandardContext
对象之后获取其中的filterCon
figs
属性值
这个属性值存放的也就是每一个filter的filterName -> applicationFilterConfig
的一个映射,其中他的value值ApplicationFilterConfig
对象中存放的是一些关于过滤器的配置内容,比如filterName / filterClass
等等信息,在前面进行filter型内存马的编写的时候也涉及到了这个类对象的创建
第二个方法是getFilterMaps
类似的是获取的是StandardContext
类对象中的filterMaps
属性
对于该属性的描述
此应用程序的过滤器映射集,按照它们在部署描述符中定义的顺序,以及通过 ServletContext 添加的额外映射,可能在部署描述符中定义的映射之前和之后。
在获取了和filter有关的信息之后,检测的主体是:
遍历所有的filter
分别获取每一个filter
的filterName / filterClass / filterClassLoaderName
等等信息
最后在结果输出中,调用了classFileIsExists
方法根据filterClass
来判断是否存在有对应的filterClass的文件资源,有,则输出对应的path路径,没有,则说明可能为内存马
同样的,我们可以结合filter内存马的注入流程
只需要设置filterMaps、filterConfigs、filterDefs就可以注入恶意的filter
filterMaps:一个HashMap对象,包含过滤器名字和URL映射
filterDefs:一个HashMap对象,过滤器名字和过滤器实例的映射
filterConfigs变量:一个ApplicationFilterConfig对象,里面存放了filterDefs
这里的检测同样是根据注入的关键点进行检测
针对tomcat下的所有的listener的获取,涉及到的方法是getListenerList
同样是在StandardContext
对象中获取他的applicationEventListenersList
属性值
这个属性值存放的一些application event listener
的列表
接下来对于检测listener型内存马的主题部分是
这里在获取了所有的listener之后,在其中筛选出了ServletRequestListener
的实现类作为检测的目标
至于什么是ServletRequestListener
? 以及除了这类listener还有什么其他类型的监听器?
这些答案在前面进行listener内存马的构造一文中能够找到
因为这里仅仅是筛选了
ServletRequestListener
的实现类进行检测,虽然常见的Listener动态创建的监听器是这个实现类,但是如果选择其他类型的监听器进行内存马的注入,这是,如果采用这种方法将会出现检测不全的情况
同样会对listener类进行是否存在类资源进行一定的检查
结合之前的listener型内存马的注入方式
内存马编写流程
首先获取到StardardContext
对象
之后创建一个实现了ServletRequestListener
接口的监听器类
再然后通过调用StardardContext
类的addApplicationEventListener
方法进行Listener的添加
这里同样是通过对应的流程进行检测
这里除了存在有是否是内存马的判断功能,还存在有servlet / filter / listener
类的下载功能以及查杀功能
直接就是在通过Repository.lookupClass
方法获取指定的类之后将其内存写入response body中返回
这里分别采用不同的方式进行kill
针对servlet
型是在deleteServlet
方法中
在简单的根据传入的HttpServletRequest / ServletClassName
获取到了urlPattern / ServletClass
等信息
通过反射调用StandardContext
对象的removeServletMapping
方法
这个方法是用来根据路由删除注册的servlet
通过反射调用StandardContext
对象的removeChild
方法
这个方法是为了不仅仅在前面删除掉路由和servlet的映射关系,也删除掉创建的对应的Wrapper
对象
而针对Filter
型的内存马,主要是在deleteFilter
方法中
获取一些关于filter的一些信息
之后就是获取FilterDef
类对象之后,反射获取他的removeFilterDef
方法,传入filterDef,也就是过滤器名字和过滤器实例的映射
进行映射的移除
之后又是获取FilterMap
类对象之后,反射获取他的removeFilterMap
方法,调用进行过滤器名字和URL映射的删除
总的来说,servlet和filter的删除主要是针对在构造的过程中的一些反方向,在构造过程中添加了什么,在kill的过程中也将要将添加的内容通过调用对应的api进行删除
通过整个对Servlet / Listener / Filter型内存马的查杀的原理分析,能够知道这种方式也就是采用直接获取所有的servlet / Listener / Filter的目标类,进而判断是否存在有文件存在来判断
针对这类型的绕过,比如前面分析到的Listener
型内存马的检查,这里仅仅是筛选了其中的一种Listener进行检测,这种方式可能会导致一些漏扫之类的问题,而且这种对于是否是内存马的判断是直接判断是否能够获取到对应类的文件,这种方式并没有判断该类文件的危害性,同样存在有漏扫和误报的情况在。