Jackson系列三——CVE-2017-17485(基于ClassPathXmlApplicationContext利用链)
/本次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 | public class PoC { |
spel.xml,放置在第三方Web服务中,看到id为pb的bean标签,指定了类为java.lang.ProcessBuilder,在其中有两个子标签,constructor-arg标签设置参数值为具体的命令,property标签:
1 | <beans xmlns="http://www.springframework.org/schema/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 | evaluate:163, StandardBeanExpressionResolver (org.springframework.context.expression) |
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 | static { |
那么为啥能修补呢?我们调试看看。
在调用BeanDeserializerFactory.createBeanDeserializer()时,其中会调用_validateSubType()函数对子类型进行校验:
在SubTypeValidator._validateSubType()函数中看到,先进行黑名单过滤,发现类名不在黑名单后再判断是否是以”org.springframe”开头的类名,是的话循环遍历目标类的父类是否为”AbstractPointcutAdvisor”或”AbstractApplicationContext”,是的话跳出循环然后抛出异常:
而我们的利用类其继承关系是这样的:…->AbstractApplicationContext->AbstractRefreshableApplicationContext->AbstractRefreshableConfigApplicationContext->AbstractXmlApplicationContext->ClassPathXmlApplicationContext
可以看到,ClassPathXmlApplicationContext类是继承自AbstractApplicationContext类的,而该类会被过滤掉,从而没办法成功绕过利用。
OK,下一篇继续其他CVE利用链的分析。