无 Java 依赖的 Shiro 靶场
2023-7-7 17:34:16 Author: Yak Project(查看原文) 阅读量:24 收藏

背景

Apache Shiro 中一个已知的反序列化漏洞——Shiro-550,它尽管存在已久,但由于其自带的加密特性和影响力,在近几年的安全演练中受到了特别关注。Yakit 中也很早就添加了 Shiro 相关的漏洞检测插件,但在 YAK 引擎的更新迭代中,一些 bug 影响了一些漏洞的检出。为了确保我们没有无意中破坏了现有的功能(这称为回归错误),我们对一些插件进行了内置化,同时结合 vulinbox 靶场当作测试靶场,对目前的几个社区呼声较高的插件来了一波 "出厂检验"。

01

Shiro RememberMe 反序列化漏洞检测

shiro 漏洞相关的原理以及利用工具已经有很多师傅分享了。如今主流的检测 Shiro Key 的方式是由 l1nk3r师傅分享的,使用一个空的 SimplePrincipalCollection序列化后和要检测的 Key 进行加密作为 payload 发送,判断响应头中的 deleteMe 个数。

在总结和学习了很多师傅的检测逻辑后,目前 Yakit shiro 插件的检测逻辑如下:

  1. 首先发送一个 Cookie: rememberMe= 随机值,记录响应头 deleteMe 的个数
  2. 判断 Set-Cookie 中的值是 rememberMe 还是 remeberMe,作为后续的关键字
  3. 结合上一步获取的关键字,开始发送使用 Key 加密后的反序列化 SimplePrincipalCollection 数据,判断 deleteMe 的个数是否小于第一步中统计的个数,如果小于(一般是减少1)则代表本次使用的 Key 正确
  4. 通过获取的正确的 Key ,使用四条最为常见的利用链配合 Header 回显,进行盲打
  5. 如果回显盲打不成功,使用 dnslog 进行利用链的探测
使用的四条回显链分别为,CB183NOCC、CB192NOCC、CCK1、CCK2
在步骤 d 中,为了保证回显的随机性,我们执行的操作是:先加载回显的 Class 文件,在 Class 常量池中找到我们预设的常量值,替换为随机值,得到修改后的 Class 字节码。
得益于 Yaklang 完善的 java 字节码相关的基础设施,上述的操作十分的简单,仅需如下几行代码即可完成:
完整的代码可以直接在 Yakit 插件商店查看
同时 Shiro 插件内也会发出至多三个风险:
当 a,b 步骤通过时,插件会发出一个风险提示——检测到 Shiro(Cookie) 框架使用
当 c 步骤通过时,插件会发出第二个风险提示——(Shiro 默认 KEY)
当 d 步骤通过时,插件会发出第三个风险提示——(Shiro Header 回显)
当 d 不通过且 e 步骤通过时,插件会发出第三个风险提示——(Shiro 利用链探测)

02

Vulinbox Shiro 靶场设计

在知道了我们 Shiro 插件的检测逻辑后,我们就可以开始写对应的靶场逻辑了,根据上面的检测逻辑,我们的靶场逻辑如下:
  1. 启动靶场时,随机使用 Shiro Key 列表中的一个当作默认的 Key,随机使用 Gadget 列表(CB183NOCC、CB192NOCC、CCK1、CCK2)中的一个,模拟可利用链

  2. 为了简单,在非在 SimplePrincipalCollection  序列化加密后的 payload 请求的响应头中添加 Set-Cookie: rememberMe=deleteMe

  3. 获取 Cookie rememberMe 的值,Base64 解码后再使用第一步中的默认 Key 进行 AES/GCM 解密

  4. 判断解密后的数据是否是一个正常的 Java 序列化对象,如果是,反序列化Java对象流,判断是否包含值为org.apache.shiro.subject.SimplePrincipalCollection 的 ClassName,如果包含,去掉响应头中的 Set-Cookie: rememberMe=deleteMe 字段

  5. 如果上一步 SimplePrincipalCollection 没有检测到,我们会根据启动时随机使用的Gadget进行判断,也就是尝试进行org.apache.commons.beanutils.BeanComparator、org.apache.commons.collections.keyvalue.TiedMapEntry、org.apache.commons.collections4.keyvalue.TiedMapEntry 的判断,以及在 Header 中回显
稍微有些复杂的是在 d,e 两步。
不过得益于 Yaklang 完善的 Java 序列化相关的基础设施,我们可以轻易地判断一个数据流是否是 Java 序列化对象流,并且将其反序列化。其实也就是早已添加在 Codec 模块中的 Java 相关的功能。
现在来到了 e 步骤 :
我们使用的四条回显链分别为,CB183NOCC、CB192NOCC、CCK1、CCK2,其中
CB183 和 CB192 的 SerialVersionUID 是不一样的,所以除了类名org.apache.commons.beanutils.BeanComparator我们也要判断 SerialVersionUID , 上图中的 serial_version字段在经过转换后,其实就是常见的 -3490850999041592962 这种 SerialVersionUID 了。
UID 和 类名的问题解决了,我们开始着手构造出符合 payload 预期结果的 fake Header 回显。
熟悉这四条链的师傅应该能知道,这四条链有一个共同点,那就是可以利用 TemplatesImpl 执行字节码,可以更加直观的感受一下:
发现了十分熟悉的 yv66,那接下来的思路就简单多了,反序列化 Java 对象流(也就是解密后的payload) -> 拿到 Class 字节码 -> 解析 Class 字节码 -> 遍历常量池,找到 setHeader 的传入值 -> 靶场 Server 响应头添加对应回显 -> Shiro 插件检出(Shiro Header 回显)风险。

03

Vulinbox Shiro 靶场漏洞检出

让我们直接启动靶场来看看实际的效果,先启动 Vulinbox 靶场 ,如下图,我们可以看到本次启动随机使用的 Shiro Key IduElDUpDDXE677ZkhhKnQ== 和 gadget CCK1:
随后我们在 Yakit 中的插件调试功能中,选择 Shiro 插件,输入 Vulinbox 靶场中的 Shiro 地址,点击执行插件。
可以发现检出了三个风险,分别是:发现 shiro 框架、发现 shiro 默认 Key 、发现 shiro 回显。
查看一下风险详细信息,Shiro Key 和利用链符合预期,响应头也存在 Vhmymv: emImhYiW。
靶场效果完美符合预期,赶紧添加一个测试用例,设置检出预期值 ,提交代码后自动跑个 CI, 有了这一层保障,也不用担心再出现回归错误啦~

04

不按套路出牌的环境

在修改完插件后,为了测试其健壮性,找了一些使用了 Shiro 框架的进行测试,发现其中一个网站连 Shiro 框架都没有探测出来。
经过测试发现必须在 body 中存在 rememberMe=1 或者 rememberMe=true 时,才会进入后续的 Cookie rememberMe 处理流程。
勾选 Shiro 插件,开启 MITM 劫持,成功检出,这种边缘情况也确实不太好处理,好在还有被动扫描的模式。

总结

本次以 Shiro 靶场为例展开说明,其实现在内置的 4 个插件,均有对应的靶场和检出预期值
最后想说说,Yak的一些基础设施库确实很好用,很多功能都可以直接拿来即用,但是也缺少了很多接口的使用文档。未来我们不仅会持续补充和完善文档,而且也会合理利用 CI/CD 配合测试,提供插件评估机制构建更加可靠的插件生态

Yakit更新记录

Yakit 1.2.2-sp1

1. webfuzzer新增标签与编解码辅助工具,支持新增常用标签

2. 修复批量发包列表加载问题

3. 恢复发包后可提取数据的功能

4. 新增靶场功能,安装即可使用(设置—试验性功能)

Yaklang 1.2.2-sp3

1. 新增插件自动评估机制的 gRPC 接口,可以自动检查插件质量

2. 新增 Web Fuzzer Tag 缓存机制

3. 优化 HTTP2 中 :authority 被强制加端口的情况

4. 修复 params 的对 JSON 的展示信息

5. 修复浏览器爬虫库的一些小问题

6. 新增靶场管理的 gRPC 接口

扫码添加客服

加入YAK技术交流群


文章来源: http://mp.weixin.qq.com/s?__biz=Mzk0MTM4NzIxMQ==&mid=2247500416&idx=1&sn=cee740b6e09735c169cc7b13aa3487cb&chksm=c2d1be24f5a63732477bb6ea3b45a71c104a91fa63ca47cd7f7b18fa7262275203cfc36af926#rd
如有侵权请联系:admin#unsafe.sh