CVE-2024-37759 SpEL注入
漏洞简介
DataGear 是一款开源免费的数据可视化分析平台,允许用户自由制作数据看板,支持接入 SQL、CSV、Excel、HTTP 接口、JSON 等多种数据源。
DataGear v5.0.0 存在 SpEL 表达式注入漏洞,可导致远程代码执行。
环境搭建
源码下载
地址:datageartech/datagear: DataGear数据可视化分析平台,自由制作任何您想要的数据看板 (github.com)
数据库准备
- 安装 MySQL 8.0 数据库,并将 root 用户的密码设置为:root(或修改 test/config/jdbc.properties 配置)
- 新建测试数据库,名称为:dg_test
- 使用 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 | |

寻找调用链过程
全局搜索evaluateVariableExpression方法
发现evaluateVariableExpressions方法(主要用于判断表达式的值是否需要计算),调用此方法
继续跟进,找到resolveExpressionIf方法调evaluateVariableExpressions方法

ai了一下此段代码含义,主要是用于判断传入的是否有表达式,会先计算表达式,再拼接到sql语句中执行
大致流程图
1 | |
- 输入值为
${user.name},系统会先替换${user.name}为真实用户名字。 - 若替换后为
SELECT COUNT(*) FROM log WHERE user='张三',再解析#{...}表达式并执行 SQL。 - 可嵌套执行,先变量、后 SQL。
继续搜索resolveExpressionIf方法,发现map方法调用此方法

此代码执行流程
1 | |
主要用于将一个原始值(可能是表达式)转换为 SqlParamValue 类型,用于数据库操作。
继续查找用法,发现org/datagear/persistence/support/DefaultPersistenceManager.java
mapToSqlParamValue方法中调用了map方法

提取出关键代码
1 | |
如果没有传入 mapper,使用默认的方式创建 SqlParamValue;
否则,调用外部提供的 mapper 的 map() 方法执行自定义映射。
这里应该控制传入mapper为ConversionSqlParamValueMapper才可调用我们需要的map方法
继续查找用法,来到的buildUniqueRecordCondition方法

没有什么特殊的条件
此方法用途
| 功能 | 描述 |
|---|---|
| 构造 SQL 条件 | 基于唯一列生成精确匹配条件 |
| 支持 null 值处理 | 自动使用 IS NULL |
| 支持嵌套 SQL | 对字面量 SQL 做合法性校验 |
| 安全 | 值通过参数绑定,避免 SQL 注入 |
生成 SQL 示例
假设唯一字段为 id, name,对应数据为:
1 | |
生成的 SQL 条件将为:
1 | |
绑定参数列表为:[1001]
继续跟进,看到get方法调用此方法

执行流程
1 | |
例如,你要从 user 表中根据 id=1001 查询一个用户:
1 | |
返回结果可能是:
- 找到唯一用户 → 返回
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 | |

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

第三步:触发漏洞执行代码
打开刚才添加的数据库,然后单击”查看”按钮,将执行 SpEL 表达式

漏洞修复
DataGear 在 v5.1.0 中修复了这个漏洞。
修复方案利用了前面提到的 ConversionSqlParamValueMapper 中的变量表达式特性。新的逻辑直接关闭了这个特性,全部按照字符串处理,不再对表达式进行解析。
注:可以使用 SimpleEvaluationContext 来限制可执行注入的 SpEL 表达式。
创建一个 受限的表达式执行环境,默认:
- 只允许读取属性、调用 getter 方法
- 不允许调用任意方法(如
System.exit()这种危险操作) - 适合暴露给不可信的用户输入(安全)