JeecgBoot 积木报表:一个忘了加登录校验的导出接口,被一条 POST 端了服务器
📢 本文摘自「凋零骷髅Sec」公众号
Q: 我就随便打开一个企业后台,怎么就把人家服务器整个端了?
A: 因为它用的 JeecgBoot 里的”积木报表”,有个导出接口忘了加登录校验。你一条 POST 过去,服务器就把 root 权限交出来了。
Q: 啥叫”积木报表”?啥又叫”未授权”?
A: 别急,往下看,这篇全是人话。
目标环境速览:
| 项目 | 值 |
|---|---|
| 平台 | JeecgBoot 3.9.3 + 积木报表(JimuReport) |
| 入口 | http://<目标地址>/(已脱敏) |
| 漏洞类型 | Pre-auth(未授权)远程命令执行 RCE |
| 一句话 | 未登录 → 枚举报表 → 导出接口注入 Aviator 表达式 → Groovy 命令执行 → 直接以 root 读 flag |
前置知识
JeecgBoot 和积木报表是啥?
JeecgBoot 是国内很流行的一套 Java 企业级低代码开发平台,很多政企后台、管理系统都是它搭的。积木报表(JimuReport)是它内置的报表引擎,你可以在网页上拖拖拽拽,几分钟做出一个带数据的报表、大屏。
一句话:JeecgBoot 是”盖楼的地基”,积木报表是地基里自带的一个”报表车间”。
“未授权 / pre-auth” 是啥?
正经的接口都要登录——请求头上带一个 Token,服务端验一下”你是不是登录过的用户”。但总有那么几个接口被”放行”了,不用登录也能访问。放行的本意是好的:有些报表、大屏要公开展示,比如访客也能看的领导驾驶舱,总不能让人家先登录吧?
问题就出在这——**”放行”这个口子开给了谁,决定了是便利还是灾难。**
Aviator / Groovy 是啥?
- Aviator 是一个 Java 的表达式引擎,用来在运行时算一些”表达式”,比如
1 + 2、"abc".length()。 - Groovy 是一种跑在 JVM 上的脚本语言,天生就能执行系统命令,比如
"ls -la".execute()。
如果一个本该”算算数字”的地方,居然能把用户输入的字符串丢给表达式引擎去执行——那用户输入啥,服务器就执行啥。这就是传说中的命令注入。
第一步:随便点开一个后台,先看看哪些接口不用登录
正经的攻击者不会去猜,先翻接口。JeecgBoot 的积木报表有个”按模板导出”的查询接口,不用登录直接 GET 就能拉出所有报表:
1 | |
浏览器直接打开这个地址,返回了 24 个报表的列表,id、名称、模板类型全在里面:

拿到了报表 id,比如 907480464532770816(主子报表循环块)。这就是我们后面要用的”报表子弹”。
第二步:验证导出接口真的”免登录”
好,光能看列表不算本事,关键是能不能不登录就触发导出。试一下导出接口:
1 | |
结果——请求头里一个 Token 都没带,接口直接进了导出流程,只是因为没有指定报表而提示”请选择需要导出的报表”:

注意对比:同一个积木报表下的其他接口(比如 queryFieldBySql、testConnection),不带 Token 一律返回 Token校验失败,无权限访问!。唯独 auto/export 被放行了。
为什么偏偏是它?因为积木报表的导出接口标了一个
@JimuNoLoginRequired注解——“本接口无需登录”。而 JeecgBoot 的 Shiro 又把/jmreport/**整体配置成了匿名(anon)。两个”放行”叠一起,未登录直接进导出流程。
第三步:把”命令执行”塞进导出参数
入口有了,但怎么把”导出”变成”执行命令”?
导出报表时,攻击者可以往 reportParams[].params 里塞任意参数。积木报表在取这些参数时,会先过一个叫 ExpressUtil.a 的处理函数——它发现参数值以 = 开头,就把等号删掉,然后把剩下的内容丢给 Aviator 表达式引擎去执行。
于是攻击者只要让参数值以 = 开头,再写一段 Aviator 能调用的代码,就能在服务器上执行命令。这里借用了官方发行包 classpath 上自带的 groovy.util.Eval——用它去拉起操作系统进程:
1 | |
响应里直接把命令输出吐了回来:

一个没登录的请求,直接读到了服务器上的 flag 文件。 flag{3ee939cf-6858-47e0-8961-2d48136cebdf}——这台服务器,已经归你指挥了。
第四步:root 权限,服务器归我了
再确认一下自己是什么身份、在哪台机器上:
1 | |

1 | |
**uid=0(root)**——JeecgBoot 进程本身就是以 root 跑的,所以这条注入执行的所有命令都是 root 权限。从”一个公开的报表导出”到”服务器 root 权限”,中间只隔了一条 POST。
顺手再查一查:
1 | |
源码解析:为什么一条 POST 就能执行命令
前面说的都是现象,现在翻源码看本质。整个链路分三层:
第一层:为什么不需要登录?
积木报表的导出接口标了注解:
1 | |
而 JeecgBoot 的 ShiroConfig 里又配了:
1 | |
两个放行叠加,未登录即可进入导出流程。
第二层:用户参数怎么变成表达式?
导出 PDF 时,攻击者传入的 reportParams[].params 会进入 getBaseSql:
1 | |
ExpressUtil.a 的逻辑是:**如果值以 = 开头,删掉全部等号,交给 Aviator compile + execute**:
1 | |
小坑提示:正因为这里会删掉所有等号,所以 payload 里千万别写 groovy 的赋值语句
x = 1(=会被吃光),要用纯链式调用。这也是为什么 PoC 里全是["命令","参数"].execute()这种写法。
第三层:Aviator 怎么变成系统命令?
Aviator 初始化时只 disableFeature(NewInstance)(禁止直接 new),但 use 引入类和调用静态方法都没禁。于是:
1 | |
use groovy.util.Eval:引入官方发行包 classpath 上的 Groovy 类Eval.me('...'):把后面的字符串当 Groovy 脚本执行["cat","/flag"].execute():Groovy 字符串列表直接拉起系统进程
一条完整的 匿名导出 → Aviator 表达式 → Groovy 命令执行 链就通了,而且是以 JeecgBoot 进程的身份(这里是 root)执行。
这个链跟已公开的几个漏洞有什么区别?
| 漏洞 | 入口 | 鉴权 | 危害 |
|---|---|---|---|
| CVE-2026-58375 / jimureport#4694 | auto/export |
匿名 | 未授权导出报表数据 |
| CVE-2026-36418 | /jmreport/executeSelectApi |
需 Token | Aviator 表达式注入(executeSelectApi 无 @JimuNoLoginRequired) |
| 本文 | auto/export |
匿名 | Aviator 表达式注入 → Groovy → 任意命令执行(RCE) |
本文这条链的关键在于:匿名入口(auto/export)+ queryParam 触发 ExpressUtil.a + Groovy 命令执行,三者缺一不可,和上面两个已公开问题不是同一个洞。
修复建议
- 导出强制鉴权:
auto/export去掉@JimuNoLoginRequired,恢复 Token 校验,别让”导出”裸奔在公网。 - 别拿用户输入当表达式:
getBaseSql不要对 HTTP queryParam 做ExpressUtil.a。用户传的是数据,不是代码,永远不要动态求值。 - 升级版本:跟进积木报表/JeecgBoot 官方安全更新,把已知的表达式注入、未授权访问一并修掉。
- 权限最小化:JeecgBoot 进程别用 root 跑,容器里也至少给个非特权用户,别让一次注入直接拿到 uid=0。
- 收口公网面:给后台入口加 IP 白名单 / 防火墙,报表、导出这类功能对公网关闭。
结尾 FAQ
问:这是不是积木报表的 bug?
算是,但更准确说是”多个放行叠出来的窟窿”。注解放行 + Shiro 匿名 + 表达式引擎裸奔,三层各开了一个口,合起来就是 RCE。
问:我不用 JeecgBoot,这篇跟我有关系吗?
有关系。这个链的精髓是通用的:**”免登录接口” + “用户输入被表达式引擎求值”**。任何”导出/预览/分享”功能只要标了匿名,又会对参数做动态求值,都可能复刻这条链。PHP、Node、Java 站点一个道理。
问:为什么 payload 里 = 会被删掉?
因为 ExpressUtil.a 的实现是 val.replace("=", "")——删光所有等号。这是它自己的特性,也正好解释了为什么 PoC 都得写成纯链式调用。顺带一提,这个”删除再求值”的细节,恰恰是分析漏洞时最容易被忽略、也最能救命的一步。
问:怎么防?
一句话:**”放行”要精确,”求值”要禁用**。导出接口老老实实鉴权;任何用户输入一律当数据不当代码。
好了,到这里,你在浏览器里敲一条 POST,就能把一台 JeecgBoot 服务器端掉。下次再看到哪个后台写着”报表/大屏免登录”,不妨多问一句:它导出的时候,会不会把我的输入当代码跑?
我们是凋零骷髅sec,回去继续下界挖矿了——下次见了,各位。
本文章复现过程全部在棉花糖无境靶场完成,仅用于安全学习与研究,严禁用于未授权目标。