Litch1大佬的这个洞有意思,终于有时间分析下,记录下分析过程
环境搭建
https://archive.apache.org/dist/druid/
1 | $wget https://archive.apache.org/dist/druid/0.20.0/apache-druid-0.20.0-bin.tar.gz && tar -xzvf apache-druid-0.20.0-bin.tar.gz |
根据参考,修改conf/druid/single-server/micro-quickstart/coordinator-overlord/jvm.config,末尾增加调试参数
1 | -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 |
背景知识
Jackson注解
name为空字符串的CreatorProperty
作者给出这个漏洞的关键点解释
漏洞的关键:
在于对用JsonCreator注解修饰的方法来说,方法的所有参数都会解析成CreatorProperty类型,对于没有使用JsonProperty注解修饰的参数来说,会创建一个name为””的CreatorProperty,在用户传入键为””的json对象时就会被解析到对应的参数上。
Demo
其中与该漏洞相关的注解如下
- JsonCreator
- JsonProperty
- JacksonInject
1 | public class Person4 { |
output 如下:
1 | JavaScriptConfig{enabled=true} |
Jackson反序列化流程跟踪
的确如Litch1所言,key为””的value({“enabled”:true})被jackson反序列化之后赋值给config。为何如此?跟踪下Jackson JsonCreator的逻辑
作者也有给出对应的关键过程
com.fasterxml.jackson.databind.deser.BeanDeserializer#_deserializeUsingPropertyBased在解析的过程中,会拿解析到的json串中的“键名”去查找当前解析对象中对应的creatorProperty,这步对应的是findCreatorProperty方法,findCreatorProperty方法会去_propertyLookup 这个HashMap中查找”键名”对应的属性,在_propertyLookup中可以看到其中没有用JsonProperty注释修饰的JavaScriptConfig的键为””,要是json串中的键也为””,就能匹配上,取出JavaScriptConfig对应的creatorProperty
简单而言,Jackson反序列化过程基本如下
- 调用mapper.readValue(jsonString, DstClass.class)
- 分析DstClass,有哪些属性Property,以及这些属性对应的set方法(creatorProperty)是什么
- 依次读取jsonString key & value,根据步骤2中key对应的set方法,调用并创建。
其中,分析DstClass,在com.fasterxml.jackson.databind.deser.BeanDeserializer这个类中实现,步骤2中的结果保存在BeanDeserializer#_beanProperties Map类里面,之后通过PropertyBeanCreator转换为BeanDeserializer#_propertyLookup
可见config对应的CreatorProperty name为””,因此_propertyLookup生成了name为””,value为config赋值函数的一条记录
步骤3中属性赋值中,根据key找对应set方法,具体实现函数为findCreatorProperty,就是从_propertyLookup寻找的,然后实现属性赋值
Rhino
javascript引擎,这里和前面比就简单了
1 | Context cx = Context.enter(); |
漏洞分析
整个漏洞的精髓有两点
1. 利用Jackson JsonCreator注解特性,覆盖JavaScriptDimFilter JavaScriptConfig config属性,开启javascript功能
覆盖config过程
1 | SamplerResource#SamplerResponse |
2. JavaScriptDimFilter#toFilter 触发回溯
作者给的poc是回溯到SamplerResource#post可用链,对应触发的调用栈如下
1 | toFilter:149, JavaScriptDimFilter (org.apache.druid.query.filter) |
漏洞修复
漏洞修复pr:https://github.com/apache/druid/pull/10818/files/1f7fc3c929a3b03fed69a03d2f3f96898c74a6d0
其中最重要的修改在core/src/main/java/org/apache/druid/guice/GuiceAnnotationIntrospector.java
1 |
|
非JsonProperty会在_propertyLookup表中生成一条记录name为””空的映射,此处直接将非JsonProperty的属性忽略,也就不会有后续的JavaScriptConfig反序列化
扩散思考
Jackson name为空字符串的CreatorProperty变量覆盖,是个通用问题,如何自动化发现
发现可覆盖的变量,如何判断是否可利用,甚至RCE?
小结
本文从漏洞产生的思路,简要分析了CVE-2021-25646形成的成因,即Jackson JsonCreator的特性,和Druid 支持Rhino引擎,导致最终覆盖javascriptconfig默认参数,可以执行RCE。其实和参考中的几篇分析文章相比没什么其他新的东西。
从漏洞挖掘思路,猜测大佬事应该是先知道Jackson JsonCreator的这个特性的,然后盯着Rhino引擎搞事情,找出利用链。如果事先未知,从Rhino这点直接去啃Jackson,那就更加佩服了。