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

360云安全  1636天前

Author:fnmsd@360云安全

Java中间件通用回显方法(针对HTTP)

作者:fnmsd

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

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

前言

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

  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中写入呢?

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

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/89118c2967cd53c244389564d2f8b3681

编译完的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. 去掉字符串连接:

image-20200609111648494

image-20200609111700933

3640字节

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

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

    3611字节

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

    3551字节

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

    3547

  3. 减少局变量的定义

    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字节

  4. 去掉调试时用的printStackTrace

    3488字节

  5. 删掉flush和waitFor

    3451字节,貌似没少多少

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

    3372

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

    3260

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

    3220

  9. 合并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,请继续往后看)

  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(打包有点问题,页面没改成最简版的):

    image-20200612010058336

    Tomcat:

    image-20200612010658901

    Jetty:

    image-20200612010838143

  2. Tomat8+Shiro反序列化

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

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

image-20200612011431160

发现问题:回显只能在高并发的环境下使用/没有回显

残笑师傅给我反馈说:只有高并发的环境下才能看到回显结果,结果中包含了:没HTTP只有命令输出的情况、没回显的情况、有回显的情况、有回显多次的情况。

这个问题是因为搜索到了NIO的队列中,由于Request加了存在cmd头的限定,所以搜索到的Request一定是你的Request,但是Response就不好说了,也就是说如果你在发这个的攻击包的时候,如果还有其它人在访问,那么很有可能,你的命令输出结果就输出到其它访问的结果中了。

之前测试的时候,只测了单一线程发送的情况,所以没发现这个问题,emm,还是得测试充分的。

模拟测试:

模拟正常访问:Burp Intruder使用Null Payloads发送1000次,并发10,固定发送间隔500ms。

访问的其实就是个404页面

image-20200621184851887

模拟攻击访问:Burp Repeater发送攻击报文

然后就可以发现,某几次攻击请求中,响应为空,但是一个普通的请求中出现了我们想要的结果:

某几次的攻击请求结果哪里都找不到,应该是找到的Response对象,还没等写入,就被销毁了。。

image-20200621185232383

image-20200621185255621

解决方法

目前翻阅了Tomcat、Weblogic、Jetty、JBOSS的代码,发现Request对象下面都带有对应的Response对象,所以既然可以准确定位到Request对象,那么Response也就有了。

(Jetty的resposne不是直接包含在request对象中,这三者都有无参的getResponse方法,直接反射调用就好了~)

PS:前几天还在p师傅的小密圈跟Kingkk师傅说觉得Weblogic的Request对象下面带Response好奇怪,结果中间件都这么干,emm,我Out了。。。

目前还是测了Tomcat/Weblogic/Jetty没有问题,JBoss懒得装了,有兴趣的师傅可以试一下。

10并发的正常访问情况下可以正常的触发Shiro反序列化回显:

image-20200621195945271

改完的代码:

https://gist.github.com/fnmsd/5c98b20cef16cf4942de0eba34dc2ad7

保留了Response的搜索分支,基本可以去掉,只是以防万一碰到没有getResponse方法还可以用。

参考:

http://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/catalina/connector/Request.html#field_summary

https://www.eclipse.org/jetty/javadoc/current/org/eclipse/jetty/server/Request.html

https://docs.jboss.org/jbossweb/latest/api/org/apache/catalina/connector/Request.html

Weblogic的Request类名为:weblogic.servlet.internal.ServletRequestImpl

Tomcat回显不稳定

ph4nt0mer师傅反馈Tomcat回显不稳定,经过测试发现:

Tomcat除了org.apache.catalina.connector.Request外,还有org.apache.catalina.connector.Request.RequestFacade这个包装类继承了HttpServletRequest接口。

RequestFacade不包含getReponse方法,无法准确获取到Response对象。

修改如下逻辑:

  1. 如果通过反射调用getResponse方法失败,就认为没有搜索到正确的Request对象,重置r字段
  2. 由于改为仅从request中获取response,所以删掉原有的response搜索逻辑。

改过的类地址:

https://gist.github.com/fnmsd/4d9ed529ceb6c2a464f75c379dadd3a8

参考:

https://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/catalina/connector/RequestFacade.html

最新评论

昵称
邮箱
提交评论