☰
【网络安全】JeecgBoot 多漏洞审计实战:从 SQL 注入到命令执行的完整链路复现
2026/10/1 6:49:24 网站建设 项目流程

1. JeecgBoot 多漏洞审计到底在审什么:从 SQL 注入到命令执行的完整链路

JeecgBoot 是基于 Spring Boot 的低代码平台,GitHub 上 star 数超过 40k,国内不少企业的后台管理系统都跑在它上面。它的后端业务走 Spring MVC,持久层用 MyBatis,模块划分清晰,属于那种“结构规整、适合拿来练审计”的目标。这次要聊的漏洞审计,核心是两类高危问题:一类是 SQL 注入,另一类是命令执行。前者是老问题的新绕过,后者是新模块引入的新洞。

如果你正在做授权范围内的安全测试,或者想系统学习 Java Web 漏洞审计的完整流程,这篇内容会带你走一遍从模块识别、代码跟进、黑名单分析到 PoC 验证的全过程。审计的目标版本是 JeecgBoot 3.9.1,这个版本新增了 AIRAG 模块并支持 MCP 协议,同时字典相关接口还在用黑名单方案做防注入。黑名单这种东西,治标不治本,只要关键词列表有遗漏,绕过就是时间问题。

我试过把整个审计过程拆成两条线:一条线盯新模块,因为新模块往往安全设计不成熟;另一条线盯历史修复点,因为修复方案本身可能就有缺陷。这两条线分别对应命令执行和 SQL 注入,下面会一步步展开。整个过程不涉及任何未授权攻击,所有验证都在本地搭建的环境里完成,你可以跟着复现。

审计前需要准备的东西不多:一份 JeecgBoot 3.9.1 的源码,一个能跑起来的本地环境,以及一个已认证的账号。认证这一步很关键,因为这次发现的两个漏洞都要求已认证用户,不是未授权就能打的。环境搭好之后,先别急着看代码,先把模块列表过一遍,找那些“天然危险”的模块。

2. TaoToken 前置准备:审计环境与 API 接入配置

在开始复现之前,先把环境准备好。本地跑 JeecgBoot 需要 JDK 8+、Maven、MySQL,这些基础依赖按官方文档配就行。但如果你想让审计过程更顺滑,比如用 AI 辅助分析代码、快速定位可疑的${}拼接点,可以接一个稳定的模型 API 来做代码理解。这里用 TaoToken 做前置配置,它的 API 地址是 https://taotoken.net/api,官网是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。

配置方式很简单,拿到 API Key 之后,在本地工具里填三个东西:Base URL、Key、Model ID。以 Claude Code 为例,配置文件通常放在~/.claude/settings.json或者项目级的.claude/settings.json,内容大概是这样:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的Key", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514" } }

如果你用的是 Cline 或者别的支持 MCP 的编辑器插件,配置项名字会不一样,但核心三件套不变:Base URL 填https://taotoken.net/api,Key 填你申请到的,Model ID 按你选的模型填。Codex 的话,auth.json里对应的是base_url和api_key字段。配置完之后,你可以让模型帮你读 JeecgBoot 的源码,比如直接问“AiragMcpServiceImpl 的 sync 方法里 endpoint 有没有做过滤”,比人眼一行行翻快很多。

这里要提醒一句:TaoToken 只是模型接入层,不是代理工具,也不涉及任何网络穿透。它的作用就是让你在本地审计时能调用模型做代码分析。API Key 的申请入口在 https://taotoken.net/api-keys,文档在 https://taotoken.net/doc。配置好之后,先跑一个简单的对话测试,确认能正常返回,再进入正式的审计环节。

环境方面,JeecgBoot 的数据库配置在application-dev.yml里,默认用的是 MySQL。启动之前记得把allow-sensitive-nodes这个配置看一眼,后面讲命令执行的时候会用到。这个配置在jeecg.ai-rag下面,默认值是sql,stdio,也就是说 stdio 节点默认是开着的。开发环境和演示环境基本都是这个默认值,这一点很关键。

3. 可复制配置:MCP 命令执行与 SQL 注入的 PoC 请求构造

先看命令执行这条线。AIRAG 模块是 3.9.1 新增的,MCP 本身涉及工具调用和进程通信,这种模块天然就是审计重点。在模块列表里找到jeecg-boot-module-airag,进去之后看AiragMcpController,里面有个saveAndSync接口。这个接口的逻辑是:先 edit 保存,再 sync 同步。问题出在 sync 的时候,endpoint字段被直接拼进了sh -c执行。

跟进AiragMcpServiceImpl的 sync 方法,stdio 分支里有一行endpoint.trim()直接塞进["sh", "-c", endpoint],中间零过滤。注释里自己都写了“endpoint 可能是一个命令行”,但没做任何安全处理。唯一的门槛是allowSensitiveNodes配置检查,而默认配置里 stdio 是开着的。

调用链简化一下是这样的:

POST /jeecg-boot/airag/airagMcp/saveAndSync → AiragMcpController.saveAndSync(@RequestBody AiragMcp) → airagMcpService.edit(airagMcp) // endpoint 持久化 → airagMcpService.sync(id) → cmdParts = ["sh", "-c", endpoint.trim()] → StdioMcpTransport → ProcessBuilder → /bin/sh -c "<endpoint>"

构造 PoC 的时候,请求体里type填stdio,endpoint填你要执行的命令。比如验证阶段可以用一个无害的命令,像id或者whoami,确认命令确实被执行了。请求示例:

POST /jeecg-boot/airag/airagMcp/saveAndSync HTTP/1.1 Host: localhost:8080 Content-Type: application/json X-Access-Token: <你的token> { "name": "test-mcp", "type": "stdio", "endpoint": "id", "status": 1 }

再看 SQL 注入这条线。JeecgBoot 的字典接口之前出过 SQL 注入,官方在 v3.4.x 加了黑名单方案修复,用的是SqlInjectionUtil.specialFilterContentForDictSql()。这次审计的核心就是分析这个黑名单到底拦了什么,有没有绕过的可能。

先看黑名单关键词列表,几个关键点:select后面要求带空格,information_schema是完整匹配,没有exists,没有and/or。再看正则部分,user\s*\(匹配的是user()带括号的形式,sleep\s*\(也在拦截列表里。

绕过思路就很清楚了:select后面用%0a换行符替代空格,select%0a1不会被检测到;user()被拦,但 MySQL 的current_user不需要括号,直接绕过;information_schema被拦,但mysql.innodb_table_stats一样可以查表名。

接下来找哪些接口用了这个黑名单过滤、哪些地方用了 MyBatis 的${}拼接。在SysDictMapper.xml里搜${},能看到好几个直接拼接的点:

<if test="key == '_tableFilterSql'"> and ${value} </if>

${filterSql}和${value}都是直接拼接,不是#{}参数化查询。审计思路就是找非预编译、参数可控、经过specialFilterContentForDictSql黑名单的接口。

第一处:/sys/dict/getDictItems/{dictCode},布尔盲注,过了黑名单但可绕。dictCode用逗号分割,第四个参数直接作为filterSql传入,过了一层黑名单然后进 Mapper 的${filterSql}。验证方式:1=1返回数据,1=2返回空。

第二处:/sys/dict/loadDict/{dictCode},LIKE 注入,连黑名单都没过。keyword参数直接拼进 LIKE 语句,没有调用任何过滤方法。PoC 参数:keyword=%' OR 1=1 OR '%'=',拼出来就是realname like '%%' OR 1=1 OR '%'='%',LIKE 条件直接被绕过返回所有数据。

第三处:/sys/dict/loadTreeData,时间盲注,JSON 透传,没过黑名单。condition参数是个 JSON,里面的_tableFilterSql直接透传到 Mapper。PoC:condition={"_tableFilterSql":"1=1 and sleep(5)"},基线耗时 0.016 秒,注入后耗时 15 秒。

同样的模式还有/sys/api/queryFilterTableDictInfo、/sys/api/queryTableDictItemsByCode、/sys/api/loadDictItemByKeyword、/sys/dict/loadDictItem/{dictCode},都是类似的问题。汇总一下:

接口注入参数类型是否过黑名单
/sys/dict/getDictItems/{dictCode}filterSql布尔盲注过了,可绕
/sys/dict/loadDict/{dictCode}keywordLIKE注入没过
/sys/dict/loadTreeData_tableFilterSql时间盲注没过
/sys/api/queryFilterTableDictInfofilterSql布尔盲注过了,可绕
/sys/api/queryTableDictItemsByCodetableFilterSql布尔盲注过了,可绕
/sys/api/loadDictItemByKeywordkeywordLIKE注入没过
/sys/dict/loadDictItem/{dictCode}dictCode(表名)布尔盲注过了,可绕

4. 验证请求与成功结果:本地环境复现与结果判读

命令执行的验证,在本地环境里发完saveAndSync请求之后,看响应里有没有命令执行的回显。如果endpoint填的是id,响应里应该能看到当前进程的用户信息。这一步的关键是确认allow-sensitive-nodes配置里包含stdio,如果配置被改过,请求会返回权限不足的提示。

SQL 注入的验证分三种类型。布尔盲注看返回数据的差异:1=1和1=2的响应内容明显不同,前者返回字典项列表,后者返回空数组。LIKE 注入看是否返回了超出预期的数据量,正常查询应该只返回匹配关键词的记录,注入之后返回全表数据。时间盲注看响应耗时,基线请求通常在几十毫秒,注入sleep(5)之后响应时间会明显拉长到 5 秒以上。

验证的时候要注意,所有请求都要带认证 Token。JeecgBoot 的认证走X-Access-Token请求头,Token 从登录接口获取。本地环境可以用默认账号登录,拿到 Token 之后再发 PoC 请求。

命令执行的成功结果判读:响应里如果包含命令的输出内容,说明sh -c确实执行了。比如endpoint填whoami,响应里出现当前用户名,就验证成功了。如果响应里只有保存成功的提示,没有命令输出,可能是 sync 过程异步执行了,需要看服务端日志确认。

SQL 注入的成功结果判读:布尔盲注对比两次请求的响应体,差异明显即存在注入。LIKE 注入对比返回记录数和正常查询的差异。时间盲注用curl的-w "%{time_total}"参数记录耗时,或者用 Burp Suite 的响应时间列观察。

这里踩过的坑是:时间盲注的sleep函数在某些 MySQL 版本里被禁用,或者sleep被黑名单正则拦截。如果sleep被拦,可以换benchmark函数,或者用select%0aif(1=1,sleep(5),0)这种形式绕过关键词检测。另外,%0a换行符在 URL 里要编码成%0a,直接写换行符会被截断。

验证完成之后,把请求和响应都记录下来,作为审计报告的证据。注意不要在非授权环境里做这些验证,所有操作都限制在本地搭建的测试环境。

5. 本篇常见错排查:401、local proxy failed、reading choices 与 OAuth 报错

审计过程中会遇到几类典型报错,这里逐个排查。

401 未授权:请求返回 401,说明 Token 无效或过期。检查X-Access-Token请求头是否带上,Token 是否从登录接口正确获取。JeecgBoot 的 Token 有有效期,过期后需要重新登录。如果用的是 API 方式调用,确认请求头名字没写错,有些版本用的是token而不是X-Access-Token。

local proxy failed:这个报错通常出现在模型 API 调用环节,不是 JeecgBoot 本身的问题。如果你在审计时用 TaoToken 做代码分析,配置里 Base URL 填错或者网络不通会报这个。检查ANTHROPIC_BASE_URL是否填的https://taotoken.net/api,Key 是否有效。如果用的是 Cline 或 Claude Code,确认配置文件路径正确,环境变量没有冲突。

reading choices 报错:这个报错一般出现在模型返回格式解析环节。如果你用模型 API 做代码审计辅助,返回的 JSON 结构不符合预期会报这个。检查 Model ID 是否填对,有些模型名称在不同接入层里写法不一样。TaoToken 的模型列表可以在 https://taotoken.net/models 查看,选一个确认可用的 Model ID。

OAuth 报错:如果你用 Claude Code 的 OAuth 登录方式,可能会遇到回调失败。这种情况建议改用 API Key 方式,在settings.json里直接填ANTHROPIC_API_KEY,不走 OAuth 流程。API Key 的获取入口在 https://taotoken.net/api-keys,拿到之后填进配置就行。

命令执行无回显:saveAndSync请求返回成功,但响应里没有命令输出。这种情况可能是 sync 过程异步执行,命令结果写到了服务端日志。去看 JeecgBoot 的启动日志,搜索StdioMcpTransport相关的输出。另外确认allow-sensitive-nodes配置里确实包含stdio,如果配置被改成只允许sql,stdio 分支不会执行。

SQL 注入被拦截:如果请求返回“非法参数”之类的提示,说明黑名单拦截生效了。检查注入 payload 里是否包含了黑名单关键词,比如select带空格、information_schema、user()带括号。换成select%0a、mysql.innodb_table_stats、current_user再试。如果还是被拦,说明这个接口走的不是specialFilterContentForDictSql,而是别的过滤逻辑,需要重新跟进代码。

时间盲注耗时不稳定:sleep(5)的响应时间有时候是 5 秒,有时候是 15 秒,这是因为数据库连接池或者网络抖动。多测几次取平均值,或者把sleep时间调大一点,比如sleep(10),减少误差干扰。

排查的时候,建议把服务端日志级别调到 DEBUG,这样能看到 SQL 语句的实际执行情况,对判断注入是否成功很有帮助。日志配置在logback-spring.xml里,把com.jeecg包的日志级别改成 DEBUG 就行。

6. 语义一致 CTA:审计完成后的模型验证与长期编码方案

审计做完之后,如果你想把这类分析流程固化下来,比如每次拿到新版本的 JeecgBoot 都能快速过一遍可疑的${}拼接点和命令执行入口,可以接一个稳定的模型 API 做辅助。TaoToken 的模型对话入口在 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=,你可以直接把源码片段贴进去,让模型帮你找可疑的拼接点。

如果你长期做代码审计或者安全开发,需要频繁调用模型做代码理解,可以看看 Coding Plan,入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。它的定位是长期编码和 Agent 场景,适合那种每天都要跟代码打交道的用法。

接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=,里面写了不同工具和语言的接入方式。API Key 的管理在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=,可以按项目创建不同的 Key,方便区分用途。

回到审计本身,这次 JeecgBoot 3.9.1 的两类问题,根因都是“信任了用户输入”。MCP 模块的endpoint直接进sh -c,字典接口的${}直接拼 SQL,黑名单只是治标。审计的时候,与其盯着黑名单找绕过,不如直接找那些“没有走参数化查询”的地方,${}在 MyBatis 里就是最明显的信号。把SysDictMapper.xml里所有${}出现的位置列出来,再逐个跟进调用链,比分析黑名单高效得多。

命令执行这条线,重点看新模块和涉及进程通信、文件操作、命令拼接的接口。MCP 协议本身就是为了工具调用设计的,stdio模式天然要执行命令,这种设计下如果没做白名单校验,出问题是迟早的事。审计的时候,把ProcessBuilder、Runtime.exec、sh -c这些关键词在代码里搜一遍,能快速定位可疑点。

最后提醒一句:所有验证都要在授权范围内做,本地环境复现是最稳妥的方式。审计报告里写清楚漏洞位置、触发条件、验证步骤和修复建议,修复建议优先推荐参数化查询和白名单校验,黑名单方案只能作为临时缓解。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询