JAVA对象让Null回显页面,基于请求/响应对象搜索的Java中间件通用回显方法(针对HTTP)...

Author:fnmsd@360云安全

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

作者:fnmsd

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

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

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

....

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

并且目前回显思路主要是基于加载类,执行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.HTTPServletResponsejava类中间件的request和response分别实现以上两个接口

从Thread.currentThread()起始搜索实现了如上两个接口的对象

通过HttpServletRequest的getHeader方法可以获取到请求头

通过HTTPServletResponse的getWriter方法可以获取到响应的Writer(开始用的ServletResponse的getOutputStream接口,但是会报重复获取的OutputStream的错误,此处就改为使用getWriter了)

都搜索到后,该执行执行,该输出输出

初始代码

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

编译完的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文件的分析:

1.初始

​ 3888字节

2.去掉可有可无的静态字段static Class ReqC = HttpServletRequest.class;

static Class RespC = HttpServletResponse.class;

static int max_depth = 50;

​ 3734字节去掉字符串连接:

47698d88c3c9ed83db9b1e52e54984be.png

6f6f956355e8bd8686a520108c2c577c.png

3640字节删除多余逻辑的限制逻辑if(depth > 50||(req!=null&&resp!=null)){

return;

}

3611字节

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

3551字节

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

3547

减少局变量的定义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字节

去掉调试时用的printStackTrace

3488字节

删掉flush和waitFor

3451字节,貌似没少多少

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

3372

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

3260

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

3220

合并Scanner相关逻辑为一句:

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

3130

以下本应有判断来进行的剪枝,改为使用异常处理兜着去掉java.lang类的搜索剪枝

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

节省了toString和startsWith

3009

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

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

2962

去掉全部isPrimitive的判断

2917

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

2908

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

2850

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

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

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

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

瘦身总结尽量减少引用的方法和类型(包括像字符串连接这种隐式调用)

尽量减少字段、局部变量的定义

能用异常处理兜底的处理,减少判断。

蚊子腿也是肉,把字段名、参数名、变量名改小点(这块不确定,可能只有字段名有效)

比较大的地方主要在ConstantPool和变量定义上(ConstantPool还好理解,变量定义不太明白,后续还是好好在学习学习Class文件结构)

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

new a();

%>

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

e359b01605f290a1d8893173b11c8f4c.png

Tomcat:

6b02d23556d9a33e48e1d928011aa72a.png

Jetty:

35bb607ee55738b4b4fad21bdbcc24ce.png

Tomat8+Shiro反序列化

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

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

ede18ce19f957eb84248945047234513.png

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

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

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

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

模拟测试:

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

访问的其实就是个404页面

a3d0e6a3bd879953b016003bbc286949.png

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

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

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

7212f1a2f1a9789d85fbcfda22704899.png

c0cf9f6791a74cb2b6e6cc2b9be5d063.png

解决方法

目前翻阅了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反序列化回显:

aafb6a48b2123ebde02a3c108b66bb42.png

改完的代码:

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

参考:

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对象。

修改如下逻辑:如果通过反射调用getResponse方法失败,就认为没有搜索到正确的Request对象,重置r字段

由于改为仅从request中获取response,所以删掉原有的response搜索逻辑。

改过的类地址:

参考:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值