基于请求/响应对象搜索的Java中间件通用回显方法(针对HTTP)

先感谢一下scz大佬给的博客推荐,受宠若惊+10086。

心血来潮有这么个想法,验证一下。

只讨论思路,工具木有~

前言

  1. 看了很多师傅们在研究针对Java中间件+Java反序列化漏洞回显的研究:

    • https://xz.aliyun.com/t/7740
    • https://xz.aliyun.com/t/7348
    • https://paper.seebug.org/1233/
    • (。。。应该还有挺多,就不翻了)
  2. 又看到了c0ny1师傅的作品:《java内存对象搜索辅助工具》

    配合IDEA在Java应用运行时,对内存中的对象进行搜索。比如可以可以用挖掘request对象用于回显等场景。

    按照经验来讲Web中间件是多线程的应用,一般requst对象都会存储在线程对象中,可以通过Thread.currentThread()Thread.getThreads()获取。

  3. 并且目前回显思路主要是基于加载类,执行static块或者构造方法(原生反序列化、FastJson、Jackson一类的都有):

    • TemplatesImpl类的反序列化链,内嵌类的bytecode,defineClass。
    • 其他反序列化链使用URLClassLoader进行远程加载类。
    • JNDI远程加载类。

所以想到: 我们能否写单个类,让它能够触发时,使用2的思路去寻找request和response,从request中获取命令参数,然后向Response中写入呢?

先说结论: 可以实现,在Tomcat、Jetty和Weblogic中都可使用(只测了这三,个人觉得其它Java中间件也没差),还测试了Shiro、Fastjson、Jackson反序列化的场景。

Tomcat6+Shiro会报java.io.StreamCorruptedException: invalid type code:错误,很迷,有空再解决吧。

精简后的编译完的class文件大小在2800-2900B左右,Shiro反序列化用的Cookie值大小可以控制在5000字节左右,勉强可以接受。

响应时间一般在3S内。

基本思路

javax.servlet.http.HttpServletRequest

javax.servlet.http.HTTPServletResponse

  1. java类中间件的request和response分别实现以上两个接口
  2. 从Thread.currentThread()起始搜索实现了如上两个接口的对象
  3. 通过HttpServletRequest的getHeader方法可以获取到请求头
  4. 通过HTTPServletResponse的getWriter方法可以获取到响应的Writer(开始用的ServletResponse的getOutputStream接口,但是会报重复获取的OutputStream的错误,此处就改为使用getWriter了)
  5. 都搜索到后,该执行执行,该输出输出

初始代码

有点多,都贴上有水字数的嫌疑,还是贴gist链接吧。┓( ´∀` )┏

https://gist.github.com/fnmsd/89118c2967cd53c244389564d2f8b368

编译完的class 3888字节,好吉利~

  • 为了类能小一些,没有做c0ny1师傅那么细致的搜索分类和剪枝。

  • 为了方便,纯粹使用深度优先搜索(DFS),依次搜索字段

  • 然而接下来为了瘦身,各种先定逻辑还得继续砍砍砍

**PS:**在搜索Tomcat的Request过程中,不知道为何会搜出一个并非当前Request的对象,所以这里限制了Request必须包含cmd头,才认为找到了真的Request,Response目前没有发现这个问题。

为代码瘦身

由于Nginx有Cookie 4096B长度的限制,Tomcat有8096B的长度限制(感谢c0ny1师傅),所以为了能使用Templates类的链,编译出的Class文件还是越小越好。

以下内容比较乱,是一个逐渐尝试减小Class文件的过程,没兴趣可以略过

整个分析过程使用classpy-0.4.jar来进行对Class文件的分析:

https://github.com/zxh0/classpy

在这里插入图片描述

1.初始

​ 3888字节

2.去掉可有可无的静态字段

    static Class ReqC = HttpServletRequest.class;
    static Class RespC = HttpServletResponse.class;       
    static int max_depth = 50;

​ 3734字节

  1. 去掉字符串连接:
    在这里插入图片描述
    在这里插入图片描述

3640字节

  1. 删除多余逻辑的限制逻辑

            if(depth > 50||(req!=null&&resp!=null)){
                return;
            }
    

    3611字节

  2. 删除getModifiers的调用(删除一个方法大概60字节的样子?)

3551字节

减少了一个空判断,大概4字节

3547

  1. 减少局变量的定义

    Class a = obj.getClass();
    if(a.isPrimitive()||a.toString().startsWith("java.lang")){
        return true;
    }
    

    变为:

    if(obj.getClass().isPrimitive()||obj.getClass().toString().startsWith("java.lang")){
                return true;
            }
    

    3525,少了22字节

  2. 去掉调试时用的printStackTrace

    3488字节

  3. 删掉flush和waitFor

    3451字节,貌似没少多少

  4. 删掉static块和start方法,只保留构造方法触发

    3372

  5. 删除PrintWriter的变量赋值,删除Test by fnmsd字符串

    3260

  6. 删除掉proc的定义,直接跟getInputStream一块调用

    3220

  7. 合并Scanner相关逻辑为一句:

    resp.getWriter().println(new Scanner(Runtime.getRuntime().exec(req.getHeader("cmd")).getInputStream()).useDelimiter("\\A").next())

    3130

以下本应有判断来进行的剪枝,改为使用异常处理兜着

  1. 去掉java.lang类的搜索剪枝

    obj.getClass().toString().startsWith("java.lang")

    节省了toString和startsWith

    3009

  2. 去掉数组包裹类型的为非primtive的判断

    obj.getClass().getComponentType().isPrimitive()

    2962

  3. 去掉全部isPrimitive的判断

    2917

  4. 去掉搜索时字段值为null的判断

    2908

  5. 给类名、字段名、方法名、局部变量名都改为一个字符

    2850

    这里改局部变量其实不起作用。

  6. 去掉是否为静态字段的判断

    if((declaredField.getModifiers()&0x00000008) == 0)

    2803(最终大小在2800-2900之间,不会差太多,后面不知道又改哪里了,到了2900左右)

瘦身总结

最终结果代码(使用构造方法触发,也可以改成Static,都一样):https://gist.github.com/fnmsd/8165cedd9fe735d7ef438b2e977af327
(前面传错了一个中间的bug版本,会重复执行重复回显,感谢l1nk3r师傅早早的帮我发现了)

  1. 尽量减少引用的方法和类型(包括像字符串连接这种隐式调用)
  2. 尽量减少字段、局部变量的定义
  3. 能用异常处理兜底的处理,减少判断。
  4. 蚊子腿也是肉,把字段名、参数名、变量名改小点(这块不确定,可能只有字段名有效)
  5. 比较大的地方主要在ConstantPool和变量定义上(ConstantPool还好理解,变量定义不太明白,后续还是好好在学习学习Class文件结构)

测试

  1. 由于都是new一个对象或者static块执行,所以偷了个懒,简单写了个jsp页面,可以看到没有任何的输出语句,只是new了一下对象:

    <%@ page import="aa.a" %>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      <%
        new a();
      %>
    

    Weblogic(打包有点问题,页面没改成最简版的):

在这里插入图片描述

Tomcat:
在这里插入图片描述
Jetty:
在这里插入图片描述

  1. Tomat8+Shiro反序列化

    这里用的Jdk7u21的链(TemplatesImpl触发)来new我写的类,rememberMe长度5036

    懒得部环境的同学,可以用Vulhub里面的Shiro环境,(SpringBoot用的嵌入式Tomcat9)但是那个得用CommonsBeanutils1的链。

在这里插入图片描述
3. Fastjson JNDI加载场景(这里用vulhub的fastjson/1.2.47-rce,SpringBoot用的嵌入式Tomcat9)

在这里插入图片描述
4. weblogic12+Jackson反序列化

这个用的我之前给Jackson提的链,可惜没混到CVE编号,嘤嘤嘤o(╥﹏╥)o
在这里插入图片描述

其他

  1. 原来设置了深度优先搜索最大50层,结果weblogic下,request搜索出来在第50层,response在request下面第51层,所以后来我最大深度改到了52层。
  2. 具体使用中使用TemplatesImpl需要类链,需要使用Javassist添加AbstractTranslet接口,当然,也可以直接implements,但是这样又多俩需要实现的接口,class会大一些,具体可以参考:https://xz.aliyun.com/t/6227

一点思考: 非HTTP的反序列化,搜Socket对象,然后从getOutputStream再往里写能不能行呢?

参考资料

师傅们的研究:

https://xz.aliyun.com/t/7740

https://xz.aliyun.com/t/7348

https://paper.seebug.org/1233/

https://github.com/c0ny1/java-object-searcher

http://gv7.me/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/

J2EE的接口文档:

https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html

https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletResponse.html

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页