手把手带你深入分析 Fastjson JDBC 调用链利用过程
1398133550745333 发表于 四川 WEB安全 1430浏览 · 2025-05-27 08:46

手把手带你深入分析 Fastjson JDBC 调用链利用过程

看了网上很多,都只是简单给了这个 paylaod,几乎没有分析,对新手很不友好,在这里给出分析,如果有误,还请指正

fastjson1.2.68 绕过

这个虽然已经介绍了很多了,这里方便初学者学习,简单说一下

前提条件

Fastjson <= 1.2.68; 利用类必须是 expectClass 类的子类或实现类,并且不在黑名单中; 漏洞复现

poc

简单地验证利用 expectClass 绕过的可行性,先假设 Fastjson 服务端存在如下实现 AutoCloseable 接口类的恶意类 VulAutoCloseable: 这里是为了测试才加这个类的,实战中是需要其他的恶意类,这里是为了更好的去分析整个流程

exp

调试分析

这里我还是从 CheckAutoType()函数开始分析 当我们的 type 为 AutoCloseable 的时候 会来到

去加载,因为我们的 map 中有我们的这个类
所以我们的 clazz 就已经赋了值,会 return clazz 然后就是获取反序列化器去反序列化它的过程

是使用最后的javabeandeserialize方法获取的,然后进入到它的deserialze方法

可以看到传入的参数信息 然后就是一系列解析我们的 parser 的过程,然后我们这里直接走到关键的方法

这时候的参数是什么呢? 我们去反过来看看

最后获取到的就是我们第二个 type

就是我们的这个第一个 type

再次进入我们的 checkAutoType 方法 这次又会在哪里加载呢?

直接来到这里,因为 expectClassFlag 为 ture,我们可以返回看看原因

因为我们的 expectClass == Closeable.class 是成立的,所以赋值为 ture,然后

就可以去加载它了,但是我们的cacheClass还是flase,是因为 boolean cacheClass = autoTypeSupport || jsonType; 都为 false

进入我们的 loadclass 方法,因为我们的 cache 为 false,所以是没有机会放入我们的 map 里面的,只能 return 了,继续往下面看

如果我们的 jsonType 为 ture 就可以加入我们的 map,但是为 fasle

然后判断返回的类是否是 ClassLoader、DataSource、RowSet 等类的子类,是的话直接抛出异常,这也是过滤大多数 JNDI 注入 Gadget 的机制

然后就是,则判断目标类是否是 expectClass 类的子类,是的话就添加到 Mapping 缓存中并直接返回该目标类,否则直接抛出异常导致利用失败,这里就解释了为什么恶意类必须要继承 AutoCloseable 接口类,因为这里 expectClass 为 AutoCloseable 类、因此恶意类必须是 AutoCloseable 类的子类才能通过这里的判断



checkAutoType()调用完返回类之后,就进行反序列化操作、新建恶意类实例进而调用其构造函数从而成功触发漏洞:


JDBC4Connection

测试代码

环境为

只要<1.2.68 都可以的

简单复现

首先起一个恶意的 mysql 服务器,放上我们的 cc 的链子就 ok

这里把数据给出来,方便测试

然后运行

就可以弹出计算器

调试分析

首先是为什么能够使用 JDBC4Connection,根据上面的绕过分析,只需要我们的类继承 AutoCloseable 就可以使用

bmth师傅的图





简单看一下调用栈

就是获取到 javabean 的反序列化器,去反序列化我们的 json 数据

然后获取所有的 field 会调用对应的 setter 方法去赋值,然后实例化我们的 bean 对象

来到构造方法

可以看到属性的值已经是赋值完成了



会进入父类 ConnectionImpl 的构造方法

会开始赋值,然后进行一波检测

再往下走会来到

进入 createNewIO 方法

然后就是一个这样的调用链



重点代码如下

进入 initializePropsFromServer->handleAutoCommitDefaults()->setAutoCommit()



初始化服务器的操作,设置自动自动提交 在setAutoCommit有执行sql语句的操作 this.session.execSQL((Query)null, autoCommitFlag ? "SET autocommit=1" : "SET autocommit=0", -1, (NativePacketPayload)null, false, this.nullStatementResultSetFactory, this.database, (ColumnDefinition)null, false); 内部使用NativeProtocol对象的sendQueryString 方法来发送查询 ((NativeProtocol)this.protocol).sendQueryString

会 return this.sendQueryPacket,继续看到这个方法

检查我们的 queryInterceptors 拦截器属性值是否为 null 不为 null,就会调用 invokeQueryInterceptorsPre 方法 随后触发该拦截器的preProcess 方法 T interceptedResultSet = interceptor.preProcess 然后会触发到 populateMapWithSessionStatusValues 方法

执行一次 SHOW SESSION STATUS 查询,并将结果返回给 ResultSetUtil.resultSetToMap

会调用 rs 的 getObject 方法

重点漏洞就在

触发了我们的漏洞

LoadBalancedMySQLConnection

测试代码

然后 payload 还是上面的那个部分



简单复现

运行代码即可



调试分析

首先还是不出意外的会来到



我们的参数如下



首先走到 LoadBalancedMySQLConnection 的构造方法

参数是 LoadBalancedConnectionProxy 代理

然后就会实例化 LoadBalancedConnectionProxy 对象,代码很长,主要对各种属性赋值,方便后续的连接

然后进入 pickNewConnection 方法发起一次新连接

关键代码如下

进入 pickConnection 方法

关键代码如下

要开始创建我们的连接代理了

createConnectionForHost

可以发现是在创建 ConnectionImpl 对象了

初始化各种属性的值然后调用 ConnectionImpl.getInstance 去实例化

之后就和 JDBC4Connection 的过程一模一样了

LoadBalancedConnectionProxy

8.0.19

测试代码

简单复现

任然使用我们一样的字节码

运行即可



调试分析

初始调用栈

这里是开始实例化我们的 ReplicationConnectionUrl 对象,主要是封装我们需要连接参数的作用

封装后的

这里我们没有传入 port,默认的就是 3306

然后回到熟悉的地方 LoadBalancedConnectionProxy

到这里就和上面一模一样了,没有任何区别

0 条评论
某人
表情
可输入 255