java系统找不到指定文件怎么解决
317
2022-08-23
渗透测试-对新型内存马webshell的研究
内存马webshell-MemoryShell
前言:你马没了
利用JavaAgent技术发现并清除系统中的内存马介绍安全行业主要讨论的内存马主要分为以下几种方式
写入测试
Servlet API 提供的动态注册机制
Filter 内存马
使用 ServletContext 添加 Filter 内存马的方法。
Servlet 内存马Listener 内存马
应用中可能调用的监听器如下
前言:你马没了
利用JavaAgent技术发现并清除系统中的内存马
Github:webshell 技术, 是由于 webshell 特征识别、防篡改、目录监控等等 针对 web 应用目录或服务器文件防御手段的介入, 导致的文件 shell 难以写入和持久而衍生出的一种“概念型”木马。 这种技术的核心思想非常简单,一句话就能概括, 那就是对访问路径映射及相关处理代码的动态注册。
这种动态注册技术来源非常久远,在安全行业里也一直是不温不火的状态, 直到冰蝎的更新将 java agent 类型的内存马重新带入大众视野 并且瞬间火爆起来。这种技术的爆红除了概念新颖外, 也确实符合时代发展潮流, 现在针对 webshell 的查杀和识别已经花样百出, 大厂研发的使用分类、概率等等方式训练的机器学习算法模型, 基于神经网络的流量层面的特征识别手段, 基本上都花式吊打常规文件型 webshell。 如果你不会写,不会绕,还仅仅使用网上下载的 jsp ,那肯定是不行的。
内存马搭上了 冰蝎和反序列化漏洞的快车,快速占领了人们的视野,成为了主流的 webshell 写入方式。 作为 RASP 技术的使用者, 自然也要来研究和学习一下内存马的思想、原理、添加方式, 并探究较好、较通用的防御和查杀方式。
安全行业主要讨论的内存马主要分为以下几种方式
• 动态注册 servlet/filter/listener(使用 servlet-api 的具体实现)
• 动态注册 interceptor/controller(使用框架如 spring/struts2)
• 动态注册使用职责链设计模式的中间件、框架的实现(例如 Tomcat 的 Pipeline & Valve,Grizzly 的 FilterChain & Filter 等等)
• 使用 java agent 技术写入字节码
写入测试
Servlet API 提供的动态注册机制
2013 年,国际大站 p2j 就发布了这种特性的一种使用方法:
Servlet、Listener、Filter 由 javax.servlet.ServletContext 去加载
无论是使用 xml 配置文件
还是使用 Annotation 注解配置
均由 Web 容器进行初始化,
读取其中的配置属性,然后向容器中进行注册。Servlet 3.0 API 允许使 ServletContext 用动态进行注册
在 Web 容器初始化的时候(即建立ServletContext 对象的时候)进行动态注册。
可以看到 ServletContext 提供了 add*/create* 方法来实现动态注册的功能。
在不同的容器中,实现有所不同,这里仅以 Tomcat 为例调试,其他中间件在代码中有部分实现。
Filter 内存马
Filter 我们称之为过滤器, 是 Java 中最常见也最实用的技术之一, 通常被用来处理静态 web 资源、访问权限控制、记录日志等附加功能等等。 一次请求进入到服务器后,将先由 Filter 对用户请求进行预处理,再交给 Servlet。
通常情况下,Filter 配置在配置文件和注解中,在其他代码中如果想要完成注册,主要有以下几种方式:
1. 使用 ServletContext 的 addFilter/createFilter 方法注册;2. 使用 ServletContextListener 的 contextInitialized 方法在服务器启动时注册(将会在 Listener 中进行描述);3.
使用 ServletContext 添加 Filter 内存马的方法。
看一下 createFilter 方法 按照注释,这个类用来在调用 addFilter 向 ServletContext 实例化一个指定的 Filter 类。
这个类还约定了一个事情,那就是
如果这个 ServletContext 传递给 ServletContextListener 的 ServletContextListener.contextInitialized 方法,
接下来看 addFilter 方法 ServletContext 中有三个重载方法,
分别接收字符串类型的 filterName 以及 Filter 对象/className 字符串/Filter 子类的 Class 对象,
提供不同场景下添加 filter 的功能, 这些方法均返回 FilterRegistration.Dynamic 实际上就是 FilterRegistration 对象。
addFilter 方法实际上就是动态添加 filter 的最核心和关键的方法, 但是这个类中同样约定了 UnsupportedOperationException 异常。
由于 Servlet API 只是提供接口定义, 具体的实现还要看具体的容器,
那我们首先以 Tomcat 7.0.96 为例,看一下具体的实现细节。 相关实现方法在 org.apache.catalina.core.ApplicationContext#addFilter 中。
可以看到,这个方法创建了一个 FilterDef 对象, 将 filterName、filterClass、filter 对象初始化进去, 使用 StandardContext 的 addFilterDef 方法将创建的 FilterDef 储存在了 StandardContext 中的一个 Hashmap filterDefs 中,
然后 new 了一个 ApplicationFilterRegistration 对象并且返回, 并没有将这个 Filter 放到 FilterChain 中, 单纯调用这个方法不会完成自定义 Filter 的注册。 并且这个方法判断了一个状态标记,如果程序以及处于运行状态中,则不能添加 Filter。
直接操纵 FilterChain 呢? FilterChain 在 Tomcat 中的实现是 org.apache.catalina.core.ApplicationFilterChain, 这个类提供了一个 addFilter 方法添加 Filter, 这个方法接受一个 ApplicationFilterConfig 对象,将其放在 this.filters 中。 答案是可以,但是没用,因为对于每次请求需要执行的 FilterChain 都是动态取得的。
那Tomcat 是如何处理一次请求对应的 FilterChain 的呢? 在 ApplicationFilterFactory 的 createFilterChain 方法中,可以看到流程如下:
• 在 context 中获取 filterMaps,并遍历匹配 url 地址和请求是否匹配;• 如果匹配则在 context 中根据 filterMaps 中的 filterName 查找对应的 filterConfig;• 如果获取到 filterConfig,则将其加入到 filterChain 中• 后续将会循环 filterChain 中的全部 filterConfig,通过 getFilter 方法获取 Filter 并执行 Filter 的 doFilter 方法。
通过上述流程可以知道,每次请求的 FilterChain 是动态匹配获取和生成的, 如果想添加一个 Filter ,需要在 StandardContext 中 filterMaps 中添加 FilterMap, 在 filterConfigs 中添加 ApplicationFilterConfig。这样程序创建时就可以找到添加的 Filter 了。
在之前的 ApplicationContext 的 addFilter 中将 filter 初始化存在了 StandardContext 的 filterDefs 中,那后面又是如何添加在其他参数中的呢?
在 StandardContext 的 filterStart 方法中生成了 filterConfigs
在 ApplicationFilterRegistration 的 addMappingForUrlPatterns 中生成了 filterMaps。
而这两者的信息都是从 filterDefs 中的对象获取的。
在了解了上述逻辑后,在应用程序中动态的添加一个 filter 的思路就清晰了:
• 调用 ApplicationContext 的 addFilter 方法创建 filterDefs 对象,需要反射修改应用程序的运行状态,加完之后再改回来;• 调用 StandardContext 的 filterStart 方法生成 filterConfigs;• 调用 ApplicationFilterRegistration 的 addMappingForUrlPatterns 生成 filterMaps;• 为了兼容某些特殊情况,将我们加入的 filter 放在 filterMaps 的第一位,可以自己修改 HashMap 中的顺序,也可以在自己调用 StandardContext 的 addFilterMapBefore 直接加在 filterMaps 的第一位。
基于以上思路的实现在 threedr3am 师傅的 文章 中有实现代码,
既然知道了需要修改的关键位置,那就没有必要调用方法去改,直接用反射加进去就好了, 其中中间还有很多小细节可以变化。
写一个 demo 模拟一下动态添加一个 filter 的过程。 首先我们有一个 IndexServlet,如果请求参数有 id 的话,则打印在页面上。
现在我们想实现在程序运行过程中动态添加一个 filter ,
提供将 id 参数的数字值 + 3 的功能(随便瞎想的功能。)
具体代码放在了 org.su18.memshell.web.servlet.AddTomcatFilterServlet 中
普通访问时,会将 id 的值打印出来
访问添加 filter。
再次访问,id 参数会被加三。
Servlet 内存马
与 Filter 相同,本小节也仅仅讨论使用 ServletContext 的相关方法添加 Servlet。 还是首先来看一下实现类 ApplicationContext 的 addServlet 方法。
与上一小节看到的 addFilter 方法十分类似。 那么我们面临同样的问题,在一次访问到达 Tomcat 时,是如何匹配到具体的 Servlet 的? 这个过程简单一点,只有两部走:
• ApplicationServletRegistration 的 addMapping 方法调用 StandardContext#addServletMapping 方法,在 mapper 中添加 URL 路径与 Wrapper 对象的映射(Wrapper 通过 this.children 中根据 name 获取)• 同时在 servletMappings 中添加 URL 路径与 name 的映射。
直接调用相关方法进行添加,当然是用反射直接写入也可以,有一些逻辑较为复杂。 测试代码在 org.su18.memshell.web.servlet.AddTomcatServlet 中, 访问这个 servlet 会在程序中生成一个新的 Servlet :/su18。
看一下效果。
Listener 内存马
Servlet 和 Filter 是程序员常接触的两个技术, 所以在网络上对于之前两小节的讨论较多,
对于 Listener 的讨论较少。但实际上这个点还是有很多师傅关注到了。
Listener 可以译为监听器,监听器用来监听对象或者流程的创建与销毁,通过 Listener,可以自动触发一些操作,
因此依靠它也可以完成内存马的实现。
先来了解一下 Listener 是干什么的,看一下 Servlet API 中的注释。
应用中可能调用的监听器如下
.servlet.Session 整体状态的监听• javax.servlet.Session 属性的监听
可以看到 Listener 也是为一次访问的请求或生命周期进行服务的, 在上述每个不同的接口中,都提供了不同的方法,用来在监听的对象发生改变时进行触发。 而这些类接口,实际上都是 java.util.EventListener 的子接口。 这里我们看到,在 ServletRequestListener 接口中,提供了两个方法在 request 请求创建和销毁时进行处理,比较适合我们用来做内存马。
除了这个 Listener,其他的 Listener 在某些情况下也可以触发作为内存马的实现.
ServletRequestListener 提供两个方法: requestInitialized 和 requestDestroyed, 两个方法均接收 ServletRequestEvent 作为参数, ServletRequestEvent 中又储存了 ServletContext 对象和 ServletRequest 对象, 因此在访问请求过程中我们可以在 request 创建和销毁时实现自己的恶意代码,完成内存马的实现。
Tomcat 中 EventListeners 存放在 StandardContext 的 applicationEventListenersObjects 属性中, 同样可以使用 StandardContext 的相关 add 方法添加。
我们还是实现一个简单的功能,在 requestDestroyed 方法中获取 response 对象,
向页面原本输出多写出一个字符串。正常访问时:
添加 Listener,可以看到, 由于我们是在 requestDestroyed 中植入恶意逻辑,那么在本次请求中就已经生效了:
访问之前的路径也生效了:
除了 EventListener,Tomcat 还存在了一个 LifecycleListener ,
当然也肯定有可以用来触发的实现类,但是用起来一定是不如 ServletRequestListener
由于在 ServletRequestListener 中可以获取到 ServletRequestEvent, 这其中又存了很多东西,ServletContext/StandardContext 都可以获取到,那玩法就变得更多了。
在 requestInitialized 中监听,如果访问到了某个特定的 URL, 或这次请求中包含某些特征(可以拿到 request 对象,随便怎么定义), 则新起一个线程去 StandardContext 中注册一个 Filter,可以实现某些恶意功能。
在 requestDestroyed 中再起一个新线程 sleep 一定时间后将我们添加的 Filter 卸载掉。
有了一个真正的动态后门,只有用的时候才回去注册它,用完就删。
可以看出 Listener 内存马的危害性和玩法的变化要大于 Filter/Servlet 内存马的.
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~