背景
Apache Shiro 中一个已知的反序列化漏洞——Shiro-550,它尽管存在已久,但由于其自带的加密特性和影响力,在近几年的安全演练中受到了特别关注。Yakit 中也很早就添加了 Shiro 相关的漏洞检测插件,但在 YAK 引擎的更新迭代中,一些 bug 影响了一些漏洞的检出。为了确保我们没有无意中破坏了现有的功能(这称为回归错误),我们对一些插件进行了内置化,同时结合 vulinbox
靶场当作测试靶场,对目前的几个社区呼声较高的插件来了一波 "出厂检验"。
01 Shiro RememberMe 反序列化漏洞检测
shiro 漏洞相关的原理以及利用工具已经有很多师傅分享了。如今主流的检测 Shiro Key 的方式是由 l1nk3r
师傅分享的,使用一个空的 SimplePrincipalCollection
序列化后和要检测的 Key 进行加密作为 payload 发送,判断响应头中的 deleteMe
个数。
在总结和学习了很多师傅的检测逻辑后,目前 Yakit shiro 插件的检测逻辑如下:
-
首先发送一个 Cookie: rememberMe= 随机值,记录响应头 deleteMe 的个数
-
判断 Set-Cookie 中的值是 rememberMe 还是 remeberMe,作为后续的关键字
-
结合上一步获取的关键字,开始发送使用 Key 加密后的反序列化 SimplePrincipalCollection 数据,判断 deleteMe 的个数是否小于第一步中统计的个数,如果小于(一般是减少1)则代表本次使用的 Key 正确
-
通过获取的正确的 Key ,使用四条最为常见的利用链配合 Header 回显,进行盲打
-
如果回显盲打不成功,使用 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 插件的检测逻辑后,我们就可以开始写对应的靶场逻辑了,根据上面的检测逻辑,我们的靶场逻辑如下:
-
启动靶场时,随机使用 Shiro Key 列表中的一个当作默认的 Key,随机使用 Gadget 列表(CB183NOCC、CB192NOCC、CCK1、CCK2)中的一个,模拟可利用链
-
为了简单,在非在 SimplePrincipalCollection 序列化加密后的 payload 请求的响应头中添加 Set-Cookie: rememberMe=deleteMe
-
获取 Cookie rememberMe 的值,Base64 解码后再使用第一步中的默认 Key 进行 AES/GCM 解密
-
判断解密后的数据是否是一个正常的 Java 序列化对象,如果是,反序列化Java对象流,判断是否包含值为org.apache.shiro.subject.SimplePrincipalCollection 的 ClassName,如果包含,去掉响应头中的 Set-Cookie: rememberMe=deleteMe 字段
-
如果上一步 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 配合测试,提供插件评估机制,构建更加可靠的插件生态。
Yak官方资源
Yak 语言官方教程:
https://yaklang.com/docs/intro/
Yakit 视频教程:
https://space.bilibili.com/437503777
Github下载地址:
https://github.com/yaklang/yakit
Yakit官网下载地址:
https://yaklang.com/
Yakit安装文档:
https://yaklang.com/products/download_and_install
Yakit使用文档:
https://yaklang.com/products/intro/
常见问题速查:
https://yaklang.com/products/FAQ