网页 综合 更新时间 2026-11-13 22:13:53

shiro反序列化 - 你好,星 - 博客园

自动蜘蛛 www.cnblogs.com 正文约 3630 字
shiro反序列化 - 你好,星 - 博客园
今天我们将从代码审计的角度,去进行shiro反序列化的研究。 https://www.cnblogs.com/nihao-xing/p/23363379 这上篇文章我们已经大致的了解了fastjson的反序列化。没看的朋友可以去看一下。 什么是shiro反序列化,它和fastjson反序列化有什么区别,以及如何进行代码审计,这篇文章我都会进行解答。 可能有的师傅就会有问题 ,现在,AI这么发达,本地部署一个agent就可以进行代码审计。 其实这是个很好的问题,我来解答一下,首先呢,
我们还是像以前一样,先解释下原理。 从apache的cc链开始转介绍 Apache Commons Collections,Collections是集合的意思, 思考:java里面的集合是什么呢
先分清图里的框:虚线 = 接口;实线黑框 = 常用实现类;普通实线框 = 抽象类。 Java集合分两大分支:Collection(单列集合,一个一个存元素)、Map(双列集合,key-value键值对)
Collection 是单列集合根接口,下面两大子接口:List、Set
补充:AbstractList、AbstractSequentialList 是抽象父类,帮实现了List大部分通用逻辑。 ListIterator 是List专属迭代器,比普通Iterator支持向前遍历。
SortedSet:带排序能力的Set子接口;排序依赖Comparable(元素自身比较) / Comparator(外部比较器)
迭代器接口,所有Collection都可以产出Iterator,用来遍历集合,CC链里很多Transformer就是靠遍历、修改集合触发调用链!
Map和Collection是平级,不是继承关系!
AbstractMap:Map的抽象父类,实现了Map大部分基础方法。
CC1、CC2 链核心类:TransformedMap、LazyMap,就来自 Apache Commons Collections,本质就是对Map做包装劫持: 当Map的setValue()、get()被调用时,自动执行绑定的Transformer,最终反射执行命令。 而TransformedMap的父类就是Map体系,和图上HashMap同属Map分支。
思考:就是在java自带的jdk里面,里面就已经包含了各种各样的集合类型,或者换句通俗易懂的话来讲,都可以叫容器类。比如list可以放字符串,map里面可以放对象,Set里面可以放各种各样的数据。这样的集合类在jd k里面都已经有了,各种各样的实现方式。为什么还要引入Apache Commons Collections(CC)?
Apache Commons Collections(CC)这个第三方包,它做的事情,是在 JDK 原生集合的基础上,包装出了增强版的 Map:TransformedMap、LazyMap 它底层依然是依赖 JDK 的 Map,但是额外加了钩子:
JDK 原生集合 = 基础容器骨架,只负责存数据; Commons Collections 的 TransformedMap/LazyMap = 装饰过的增强容器,复用原生 Map 的结构,新增了自动回调钩子,反序列化的时候这个钩子就会被触发,一路走到命令执行。 思考 1.哪里出现了可以执行任意代码的问题? 2.反序列化的payload怎么构造?
核心口号:一次编译,到处运行。 .class字节码不是直接给CPU跑的机器码,是给JVM看懂的中间指令。Windows、Linux、Mac各自装对应版本JVM,同一份class文件都可以执行。
⚠️编译阶段不运行代码,只翻译。 反序列化漏洞和编译无关,漏洞是程序运行阶段(JVM加载class之后)才触发。
的反序列化、CC链关联 这个运行流程,刚好能解释Gadget为什么能跑:
序列化/反序列化,是JVM运行期、内存对象的操作,不经过javac重新编译。Payload本质只是构造对象的二进制描述,利用已经加载在JVM里的恶意Gadget类的逻辑。
这四个就是CC1链最核心的组件,全部实现了Transformer接口,是整条Gadget的骨架
幻灯片原文:利用Java反射机制来创建类实例 更准确完整解释: 它的transform()方法,接收一个对象,通过反射调用这个对象的指定方法,可以传入方法名、参数类型、参数值。 这是整条链命令执行的核心。 示例:
注意:它不只是创建实例,核心能力是反射调用任意对象的任意方法。
幻灯片原文:实现了Transformer链式调用,我们只需要传入一个Transformer数组,ChainedTransformer就可以实现依次的去调用每一个Transformer的transform()方法 通俗解释: 串联器,把一堆Transformer串成流水线。 上一个transform的返回结果,作为下一个transform的入参,顺序执行。 CC1的链路: ConstantTransformer(拿到Runtime.class) → InvokerTransformer.getMethod("getRuntime") → InvokerTransformer.invoke拿到Runtime实例 → InvokerTransformer.exec执行命令 全部打包放进ChainedTransformer数组,一键顺序跑通。
幻灯片原文:transform()返回构造函数的对象 修正+通俗解释: 固定返回你传入的那个对象,不管传入什么输入,transform永远返回预先指定的常量对象。 CC里用法:
无论传什么进来,都返回Runtime.class,作为整条链的起点输入。
幻灯片没有写备注,这里补上重点: 它是包装增强Map(装饰器模式),底层包装一个原生HashMap。 当它的setValue()方法被触发时,会自动调用我们提前绑定好的Transformer的transform()。 这就是触发入口:反序列化过程中,readObject流程里会触发Map的setValue,进而调用ChainedTransformer,启动整条命令执行链。
CC1 依赖 JDK 8u71 之前的 AnnotationInvocationHandler,这个入口在 JDK 8u71 之后就被修了(AnnotationInvocationHandler 不再直接调 setValue)。
思考:什么是反射,编程语言是否都有反射这个机制
正常正向写Java代码的时候:编译期你就知道要调用哪个类、哪个方法,直接写死调用:
编译器在javac阶段就清楚Runtime是什么类、exec是什么方法。
而反射:在代码运行的时候(JVM运行期),动态去获取一个类的信息、实例化对象、调用方法、读写属性。 类名、方法名可以是字符串,编译阶段代码根本不知道要操作哪个类。
CC链里的InvokerTransformer,底层就是反射! 它接收字符串 "exec",运行时再去找到这个方法并且执行。
正向调用:写代码的时候就确定调用逻辑;反射:运行时动态发现、调用类和方法。
不是所有语言都支持反射,反射是一套运行时自省(introspection)+动态调用的能力,不同语言支持程度差别很大:
一个关键区分:自省 ≠ 完整反射 有些语言只能拿到对象的类型信息(自省),但是不能动态调用方法、构造对象,这不叫完整反射。
CC链的RCE本质就是反射带来的能力被恶意利用:
反射本身不是漏洞,它是Java的正常特性;漏洞是不可信的输入可以控制反射调用的目标。
底层补的差不多了,开始审计。 本期的项目文件:RuoYi-4.6.0
Step 1:确定 Shiro 版本 —— pom.xml
审计目标:确认 Shiro 版本是否小于 1.8.0(Shiro-550 影响版本)。
实际文件:F:\daimashenji\RuoYi-4.6.0\pom.xml 将来如果嫌组件难翻的话,直接把pom.xml丢给ai,看有哪些危险组件 然后呢,扯句题外话,我希望大家每天能早起15分钟。原因很简单,就是你比别人早起15分钟,你就比别人早上多了15分钟的准备时间。就拿我早上上水课举例子,那节课的教室里就只有一个充电的桩位,我每次周一都会比同班的人早起15分钟去抢那个东西。他们大约是7点半起床,我是7点15起床,我每次都能吃完早饭并且还能抢到。因为早起的人不多,早起带来的收益确是滚雪球一样的,起的早,早饭排队的人就少,能比别人更早吃上早饭,还能抢到充电的桩位,不然,你们看到这篇文章还得等到今天晚上。
Step 2:查找 rememberMe 密钥 思考:我要怎么找到密钥
访问原文 返回搜索 https://www.cnblogs.com/nihao-xing/p/23433100
本页为82搜索生成的网页快照,内容来自站内索引;演示数据的原文链接仅作示例。