被高估的 RCE 与被低估的物理控制:JimuReport 攻防复盘与源码证伪
2026-11-6 15:38:3 Author: xz.aliyun.com(查看原文) 阅读量:0 收藏

在最近的一次 SRC 项目里,笔者遇到了一个基于若依(RuoYi)二次开发的高校能源管控平台。通过 JimuReport 组件的未授权配置漏洞,我们不仅获取了核心业务数据,顺藤摸瓜获得了数据库访问权限,还发现了一个能直接下发物理设备指令的未授权接口。

在复盘该漏洞时,笔者遇到了一个技术分歧:目前业界有较多文章声称该模块存在参数注入漏洞,可导致远程代码执行(RCE)。然而,在实战评估中,准确界定漏洞的真实危害边界比盲目相信网传 PoC 更为重要。本文将分为两部分:第一部分记录真实靶标上的业务级降维打击;第二部分则是笔者针对底层字节码的逆向分析,还原那个被高估的“RCE 幻象”被拦截的真实原因。


第一部分:真实靶标的降维打击(未授权与业务沦陷)

一、突破:被 Shiro 放行的报表引擎

目标系统的 /prod-api/ 路径下暴露了大量积木报表(JimuReport)的接口。按若依常见的配置,ShiroConfig 中为积木报表的路径配置了匿名白名单:

filterChainDefinitionMap.put("/jmreport/**", "anon");
filterChainDefinitionMap.put("/drag/**", "anon");

在 Shiro 的安全模型中,anon 的语义是将匹配该路径的请求直接短路,跳过后续的认证与授权过滤器链(如 FormAuthenticationFilter 和 AuthorizationFilter)。这意味着访问 /drag/ 或 /jmreport/ 下的接口时,认证链路直接被绕过。
源码:

我们对 /prod-api/drag/onlDragDatasetHead/edit 发起了修改数据集名称的请求:

POST /prod-api/drag/onlDragDatasetHead/edit HTTP/1.1
Content-Type: application/json

{"id":"970251620641243136","name":"test"}

响应包如下:

{"success":true,"message":"更新成功!","code":200}

请求未携带任何 Cookie 或 Token,报表配置写操作即告成功。攻击者可进一步利用 /drag/page/edit 向报表页面注入恶意脚本或钓鱼链接,实施水坑攻击。

二、窃取:从配置文件到数据源的沦陷

顺藤摸瓜,我们在 DataSource 相关接口中发现了 /dragonlDragDataSource/list,该接口未授权返回了数据源配置信息:

{
  "success": true,
  "result": {
    "records": [{
      "dbUrl": "jdbc:mysql://<target-host>:3306/jimureport?...",
      "dbUsername": "root",
      "dbPassword": "[REDACTED]"
    }]
  }
}

JimuReport 内置的对称加密密钥硬编码在依赖包中。获取密文后,通过提取本地 jar 包中的密钥即可还原明文密码。获取该数据库凭据后,若该数据库与业务系统同库,攻击者可绕过应用层逻辑,直接修改 sys_user 表接管系统。不过这次的目标没有条件,呜呜

三、深水区:致命的设备控制指令未授权

真正将威胁等级推向高危的,是我们在审计 OpenAPI 模块时发现的 /openapi/device/{sn}/cmd 接口:

POST /prod-api/openapi/device/941260100259/cmd HTTP/1.1
Content-Type: application/json

{}

返回:

{"msg":"functionCode不能为空","code":400}

该接口未进行任何身份认证,仅因缺少业务参数 functionCode 而拒绝执行。攻击者补齐该参数后,即可通过该接口向全校电表下发控制指令。物理世界的断电或过载等破坏性操作,是此漏洞最核心的威胁。之后可以在找到具体的功能码,这里不遍展现。保护自己也保护你们哟


第二部分:源码拆解——为什么“参数注入 RCE”打不通?

在真实靶标上,通过未授权访问已获取了足够的信息。但为彻底评估风险,笔者对 jimureport-2.5.0 及其底层依赖 aviator-5.2.6 进行了逆向分析,以验证网传的参数注入 RCE 是否成立。

四、参数流转的完整调用链

导出接口挂载了 @JimuNoLoginRequired 注解(@Retention(RUNTIME)),通过反射机制绕过鉴权。参数从 JimuReportAutoExportVO 的 reportParams 流入报表引擎的调用链如下:

getBaseSql(JmReportDb, JSONObject, Map, String)
  ↓
ExpressUtil.a(String, Map) → String
  ↓
AviatorEvaluatorInstance.compile() / Expression.execute()

从字节码角度,getBaseSql 内部对 params 的处理并不是简单的字符串拼接。在 desreport/service/a/g.class 里,参数传入后会先经过 ExpressUtil.a(String, Integer, Map) 做表达式解析预处理,再交给 AviatorEvaluatorInstance.compile()。这个过程里,如果参数值以 = 开头,会被优先识别为表达式,而不是普通数据。这也是为什么攻击者能通过 reportParams 注入表达式的根本原因。

需要说明的是,compile() 方法在编译时会检查是否触发了 feature 限制。但由于 JimuReport 初始化时只关掉了 NewInstance,理论上 StaticMethods 是允许的。但问题在于,编译阶段不报错不代表执行阶段能通过,语法解析器的限制是在执行阶段生效的。


参数最终确实被传递至 compile 和 execute 方法。

五、字节码层面的限制分析

查看 ExpressUtil 的静态初始化块(<clinit>):

i = AviatorEvaluator.newInstance();
i.useLRUExpressionCache(1000);
i.setCachedExpressionByDefault(true);
i.setOption(Options.TRACE_EVAL, false);
i.setOption(Options.ALWAYS_PARSE_FLOATING_POINT_NUMBER_INTO_DECIMAL, true);
i.disableFeature(Feature.NewInstance);   // 全文唯一一处 feature 限制

Aviator 5.2.6 的 Feature 枚举包含 17 个值,涵盖 Use、StaticMethods 等能力。JimuReport 仅禁用了 NewInstance,这意味着 StaticMethods 在逻辑上处于开启状态。

笔者在本地使用 aviator-5.2.6.jar 复现了初始化逻辑,并在测试用例中构造了多种表达式:

表达式

执行结果

System.getProperty("java.version")

✅ 返回 17.0.20.1

Runtime.getRuntime()

✅ 返回 Runtime 实例

Class.forName("java.lang.Runtime")

✅ 返回 Class 对象

r.exec("id")

❌ FunctionNotFoundException: r.exec

c.getMethod("getRuntime", nil)

❌ FunctionNotFoundException: c.getMethod

Runtime.getRuntime().exec("id")

❌ ExpressionSyntaxErrorException

语法解析层的硬限制:
Aviator 5.2.6 的解析器在处理 a.b() 形式时,会将 a.b 视作一个单一的 Identifier 去检索。因此,它支持 System.getProperty(...) 这种静态方法调用,但在获取到对象实例后,解析器无法将其识别为方法接收者,从而拒绝解析 r.exec(...) 这种实例方法链。反射链 Class.forName(...).getMethod(...) 同样在第二个 . 处发生语法解析异常。

注册函数的能力审查:
进一步审计 addFunction 的注册列表:



Sum, Avg, Max, Min, RowNum, Case, IfError,
DateFormat, DayFormat, HourFormat ...

所有注册的自定义函数均为数学计算与日期格式化类,未提供任何具备反射、命令执行或 IO 操作能力的函数。Aviator 内置的 IoModule 也未在此实例中注册。

基于上述分析,


网传的“参数注入 RCE”在 jimureport-2.5.0 + aviator-5.2.6 的组合下,由于语法解析机制和函数注册面的双重限制,实际无法实现任意命令执行(限制有点大)。


总结与修复

在实际业务环境中,未授权的配置篡改、数据源凭据泄露、物理设备指令接口暴露等业务级风险,其真实破坏力往往高于难以落地的 RCE。防御的重心应优先放在访问控制与敏感数据保护上。

修复建议:

  1. 访问控制:将 /drag/** 与 /jmreport/** 强制纳入统一鉴权体系,移除生产环境的 anon 配置。

  2. 凭据保护:数据库密码采用非对称加密或 KMS 管理,移除代码中的硬编码密钥。

  3. 接口审计:对 /openapi/** 尤其是具备指令下发能力的接口实施强身份认证。

  4. 表达式引擎:虽然当前版本 RCE 不成立,但升级组件时仍需审查 Feature 限制策略,按最小权限原则配置。

参考资料

  • JeecgBoot 官方 Issue #9837:/jmreport/auto/export 未授权 RCE 披露(2026-08-20),含漏洞链路描述与官方修复建议

  • 白帽汇安全研究院:JeecgBoot 积木报表 /jmreport/auto/export 未授权 RCE 漏洞分析(2026),含 FOFA 测绘数据与排查建议

  • CVE-2026-22733:Spring Security / CloudFoundry Actuator 认证绕过(CVSS 8.1),本文用于横向对比“两道门”架构缺陷

  • CVE-2026-36418:JimuReport executeSelectApi 端点 Aviator 表达式注入(≤ 2.3.4),本文用于说明同类 Aviator 风险的不同入口

  • JimuReport v2.5.4 安全加固发布说明:数据源密码加密升级、jeecg.jmreport.db-secret-key 配置项引入

  • JeecgBoot 3.9.3 源码:ShiroConfig.java、ExpressUtil、c/b/c 反射分发器

  • jimureport-spring-boot4-starter-2.5.0.jar 字节码:ExpressUtil.<clinit>、automate/service/a/a


文章来源: https://xz.aliyun.com/news/93194
如有侵权请联系:admin#unsafe.sh