0day--JeecgBoot v3.9.1 多漏洞审计过程
字数 5163
更新时间 2026-04-03 12:06:13

JeecgBoot v3.9.1 多漏洞审计教学文档

前言

本教学文档旨在详细剖析 JeecgBoot v3.9.1 版本中存在的多个安全漏洞。JeecgBoot 是一个在 GitHub 上拥有 41k Star 的低代码开发平台,广泛应用于国内企业。本次审计主要针对其新增的 AIRAG 模块和已进行过安全修复的字典接口,共发现两类严重漏洞:AIRAG MCP 模块的命令执行漏洞和字典接口的 SQL 注入漏洞(绕过了历史修复方案)。文档将深入讲解漏洞成因、利用条件、挖掘过程及技术细节,以供安全研究、代码审计和防御加固参考。

项目背景与技术栈

  • 项目名称:JeecgBoot
  • 版本:v3.9.1
  • 技术架构:标准 Spring Boot 结构,后端业务基于 Spring MVC,持久层使用 MyBatis。
  • 新增模块:v3.9.1 版本引入了 AIRAG 模块,该模块支持 MCP (Model Context Protocol) 协议,用于工具调用和进程间通信。
  • 历史安全背景:项目的字典相关接口曾存在 SQL 注入漏洞,官方在 v3.4.x 版本中采用了黑名单过滤方案进行修复。

漏洞一:AIRAG MCP 命令执行漏洞 (CVE-2026-xxxxx)

漏洞描述

在 AIRAG 模块的 saveAndSync 接口中,用户可控的 endpoint 字段在 stdio 模式下被未经任何过滤直接拼接进 sh -c 命令中执行,导致已认证的攻击者可实现远程命令执行 (RCE)。

前置条件

  1. 身份认证:攻击者需拥有一个有效的已认证用户会话。
  2. 配置开启:目标系统的配置文件 (application.yml 等) 中,jeecg.ai-rag.allow-sensitive-nodes 配置项必须包含 stdio。该配置在开发和演示环境中默认开启。

漏洞挖掘与分析过程

  1. 目标定位:审计时首先关注新增模块 jeecg-boot-module-airag。由于 MCP 协议涉及工具调用和进程通信,此类模块是安全审计的天然重点。
  2. 漏洞接口:在 AiragMcpController.java 中发现了 saveAndSync 接口。该接口逻辑为:先调用 edit 方法保存实体,再调用 sync 方法进行同步。
  3. 实体分析:查看 AiragMcp 实体类,其 typeendpoint 字段均通过 @RequestBody 接收,完全用户可控。注释明确说明 “stdio类型为命令”。
  4. 漏洞触发点:跟进 AiragMcpServiceImpl.java 中的 sync 方法。在 type"stdio" 的分支中,存在以下关键代码:
    // endpoint.trim() 直接拼接入命令数组
    String[] cmdParts = new String[]{"sh", "-c", endpoint.trim()};
    
    攻击者控制的 endpoint 字段在去除首尾空格后,直接被放入 ProcessBuilder 的命令参数中,最终由 /bin/sh -c 执行,中间零过滤
  5. 配置检查:代码中有一处 allowSensitiveNodes 配置检查,但默认配置 allow-sensitive-nodes: sql,stdio 即已包含 stdio,因此该检查在默认环境下不构成防御。

漏洞利用调用链

  1. POST /jeecg-boot/airag/airagMcp/saveAndSync
  2. AiragMcpController.saveAndSync(@RequestBody AiragMcp)
  3. airagMcpService.edit(airagMcp) // 将包含恶意命令的 endpoint 持久化到数据库
  4. airagMcpService.sync(id)
  5. cmdParts = ["sh", "-c", endpoint.trim()]
  6. StdioMcpTransportProcessBuilder → 执行 /bin/sh -c "<恶意命令>"

修复建议

  • endpoint 字段进行严格的输入校验和白名单控制,仅允许特定的、安全的命令或参数。
  • 避免将用户输入直接拼接进操作系统命令。如必须执行命令,应使用参数化传递,并对参数进行转义。
  • 考虑在正式生产环境中关闭 stdio 等敏感节点的默认启用。

漏洞二:SQL注入漏洞(绕过历史修复)

漏洞背景

JeecgBoot 的字典接口此前已曝出 SQL 注入漏洞。官方在 v3.4.x 版本中通过 SqlInjectionUtil.specialFilterContentForDictSql() 方法引入了黑名单过滤方案进行修复。本次审计发现,该黑名单存在绕过方法,并且部分接口甚至完全未应用此过滤。

历史防护机制分析

首先分析黑名单的实现 (SqlInjectionUtil.java):

  1. 关键词黑名单:包含 select (注意后面有空格)、union information_schema 等。关键缺陷在于:
    • select 后必须跟空格才被拦截。
    • information_schema完整匹配
    • 未拦截 existsandor 等关键词。
  2. 正则表达式黑名单:包含 chr\s*\(sleep\s*\(user\s*\( 等。关键缺陷在于:
    • user\s*\( 匹配的是 user() 带括号的形式。
    • 同样存在对空格 (\s*) 的依赖。

黑名单绕过手法

基于上述分析,可构造以下绕过Payload:

  1. 绕过 select :使用 %0a (换行符) 或 %0d (回车符) 等空白字符替代空格。例如:select%0a1
  2. 绕过 user():使用无需括号的 MySQL 内置函数或变量,如 current_useruser (不带括号)、@@version
  3. 绕过 information_schema:使用 MySQL 中其他包含元数据的系统表,如 mysql.innodb_table_statssys.schema_auto_increment_columns
  4. 利用未过滤关键词:直接使用 andorexists 等未被拦截的关键词进行布尔逻辑判断。

漏洞接口详情汇总

审计发现多处接口存在 SQL 注入,根因在于 MyBatis 映射文件 (SysDictMapper.xml) 中直接使用 ${} 进行字符串拼接,而非安全的 #{} 参数化查询。主要分为两类:应用了可绕过的黑名单过滤未应用任何过滤

下表汇总了部分存在漏洞的接口:

接口路径 注入参数 注入类型 是否过黑名单 利用要点
/sys/dict/getDictItems/{dictCode} filterSql 布尔盲注 是 (可绕过) dictCode 以逗号分割,第4部分作为 filterSql 传入。
/sys/dict/loadDict/{dictCode} keyword LIKE注入 keyword 直接拼接入 LIKE 语句,无任何过滤。
/sys/dict/loadTreeData _tableFilterSql (在condition JSON中) 时间盲注 condition JSON 中的 _tableFilterSql 值直接透传至 ${value}
/sys/api/queryFilterTableDictInfo filterSql 布尔盲注 是 (可绕过) 同第一类接口。
/sys/api/queryTableDictItemsByCode tableFilterSql 布尔盲注 是 (可绕过) 同第一类接口。
/sys/api/loadDictItemByKeyword keyword LIKE注入 同第二类接口。
/sys/dict/loadDictItem/{dictCode} dictCode (作为表名) 布尔盲注 是 (可绕过) 表名直接拼接。

典型漏洞代码分析

案例1:/sys/dict/getDictItems/{dictCode} (可绕过的黑名单)

  • Controller层 (SysDictController.java):将 dictCode 按逗号分割,第4部分作为 filterSql 传入 Service 层。
  • Service层 (SysDictServiceImpl.java):对 filterSql 调用 SqlInjectionUtil.specialFilterContentForDictSql(filterSql) 进行过滤(可绕过)。
  • Mapper层 (SysDictMapper.xml):过滤后的 filterSql 直接通过 ${filterSql} 拼接进 SQL 语句。
  • 利用Payload示例
    • 正常请求:/getDictItems/sys_user,realname,username,1=1 返回数据。
    • 异常请求:/getDictItems/sys_user,realname,username,1=2 返回空,构成布尔盲注。

案例2:/sys/dict/loadDict/{dictCode} (未过黑名单)

  • Service层 (SysDictServiceImpl.java):keyword 参数未经任何过滤,直接拼接进 LIKE 语句:
    keywordSql = "("+text + " like '%"+keyword+"%' or "+ code + " like '%"+keyword+"%')";
    
  • 利用Payload示例keyword=%' OR 1=1 OR '%'='
    • 拼接后SQL:... WHERE (realname like '%%' OR 1=1 OR '%'='%' ...)LIKE 条件被绕过,返回所有数据。

案例3:/sys/dict/loadTreeData (JSON透传,未过黑名单)

  • Service层condition 参数为 JSON 字符串,解析后直接放入 Map,其中的 _tableFilterSql 键值对直接透传到 Mapper。
  • Mapper层:通过 ${value} 直接拼接,无任何过滤。
  • 利用Payload示例condition={"_tableFilterSql":"1=1 and sleep(5)"}
    • 请求耗时从基线的 ~0.016秒 变为 ~15秒,构成时间盲注。

修复建议

  1. 根本性修复:将所有 MyBatis 映射文件中的 ${} 动态拼接替换为 #{} 预编译(参数化查询)。这是解决 SQL 注入最彻底的方法。
  2. 弃用黑名单:黑名单过滤方案存在固有的被绕过风险,不应作为主要防御手段,仅可作为辅助的深度防御措施。
  3. 输入校验:对所有用户输入的参数进行严格的类型、格式、长度和业务逻辑校验。
  4. 最小权限原则:数据库连接账户应遵循最小权限原则,避免使用高权限账号运行应用。

总结

本次对 JeecgBoot v3.9.1 的审计揭示了两类高危漏洞:

  1. AIRAG MCP 命令执行漏洞:源于新引入模块对用户输入 (endpoint) 的极度不信任处理,直接拼接进 Shell 命令。利用门槛低(已认证+默认配置),危害极高。
  2. SQL注入漏洞集群:根源在于持久层框架(MyBatis)错误地使用了 ${} 进行 SQL 拼接。历史修复采用的黑名单方案存在明显缺陷,可通过换行符、无括号函数、替代系统表等方式绕过。更严重的是,部分接口完全跳过了该黑名单过滤,导致直接可注入。

这两类漏洞的共性在于:对用户输入缺乏充分的校验和安全的处理方式。开发者在引入新功能(MCP)和修复旧漏洞时,都未能严格遵守安全编码规范,导致了严重的安全隐患。这提醒我们,安全修复必须触及根源(如将动态拼接改为参数化查询),并需进行完整的回归测试,避免修复不彻底或引入新问题。

相似文章
相似文章
 全屏