ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

【网络安全】JeecgBoot 多漏洞审计实战:从 SQL 注入到命令执行的完整链路复现

2026/10/1 6:49:25 拓冰建站 浏览量
【网络安全】JeecgBoot 多漏洞审计实战:从 SQL 注入到命令执行的完整链路复现 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_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_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/apiKey 填你申请到的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填stdioendpoint填你要执行的命令。比如验证阶段可以用一个无害的命令像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 testkey _tableFilterSql and ${value} /if${filterSql}和${value}都是直接拼接不是#{}参数化查询。审计思路就是找非预编译、参数可控、经过specialFilterContentForDictSql黑名单的接口。第一处/sys/dict/getDictItems/{dictCode}布尔盲注过了黑名单但可绕。dictCode用逗号分割第四个参数直接作为filterSql传入过了一层黑名单然后进 Mapper 的${filterSql}。验证方式11返回数据12返回空。第二处/sys/dict/loadDict/{dictCode}LIKE 注入连黑名单都没过。keyword参数直接拼进 LIKE 语句没有调用任何过滤方法。PoC 参数keyword% OR 11 OR %拼出来就是realname like %% OR 11 OR %%LIKE 条件直接被绕过返回所有数据。第三处/sys/dict/loadTreeData时间盲注JSON 透传没过黑名单。condition参数是个 JSON里面的_tableFilterSql直接透传到 Mapper。PoCcondition{_tableFilterSql:11 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 注入的验证分三种类型。布尔盲注看返回数据的差异11和12的响应内容明显不同前者返回字典项列表后者返回空数组。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(11,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/apiKey 是否有效。如果用的是 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如果配置被改成只允许sqlstdio 分支不会执行。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_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content你可以直接把源码片段贴进去让模型帮你找可疑的拼接点。如果你长期做代码审计或者安全开发需要频繁调用模型做代码理解可以看看 Coding Plan入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。它的定位是长期编码和 Agent 场景适合那种每天都要跟代码打交道的用法。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content里面写了不同工具和语言的接入方式。API Key 的管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content可以按项目创建不同的 Key方便区分用途。回到审计本身这次 JeecgBoot 3.9.1 的两类问题根因都是“信任了用户输入”。MCP 模块的endpoint直接进sh -c字典接口的${}直接拼 SQL黑名单只是治标。审计的时候与其盯着黑名单找绕过不如直接找那些“没有走参数化查询”的地方${}在 MyBatis 里就是最明显的信号。把SysDictMapper.xml里所有${}出现的位置列出来再逐个跟进调用链比分析黑名单高效得多。命令执行这条线重点看新模块和涉及进程通信、文件操作、命令拼接的接口。MCP 协议本身就是为了工具调用设计的stdio模式天然要执行命令这种设计下如果没做白名单校验出问题是迟早的事。审计的时候把ProcessBuilder、Runtime.exec、sh -c这些关键词在代码里搜一遍能快速定位可疑点。最后提醒一句所有验证都要在授权范围内做本地环境复现是最稳妥的方式。审计报告里写清楚漏洞位置、触发条件、验证步骤和修复建议修复建议优先推荐参数化查询和白名单校验黑名单方案只能作为临时缓解。