SPEL,全称为Spring表达式语言,是一个由 Spring 框架提供的表达式语言。它是一种基于字符串的表达式语言,可以在运行时对对象进行查询和操作。
SpEL 支持在XML和注解配置中使用,它可以在Spring框架的各种组件中使用,如Spring MVC 控制器、Spring Security 安全框架、Spring Data 数据访问框架等。它可以方便地访问对象的属性、调用对象的方法、进行数学运算、逻辑运算、正则表达式匹配等操作。
使用 SpEL 可以大大简化编程工作,使得配置和编写代码更加灵活和易于维护。例如,可以使用 SpEL 在 XML 或注解中配置 Spring bean 的属性,或在 Spring MVC 控制器中动态地计算请求参数。总而言之,SpEL 是 Spring 框架中一个非常有用的特性,它提供了一种灵活、简洁的方式来操作和查询对象。
SpEL 表达式语言支持以下功能:
- Literal expressions(字面量表达式)
- Boolean and relational operators(布尔和关系运算符)
- Regular expressions(正则表达式)
- Class expressions(类表达式)
- Accessing properties, arrays, lists, maps(访问属性、数组、列表、映射)
- Method invocation(调用方法)
- Relational operators(关系运算符)
- Assignment(赋值运算符)
- Calling constructors(调用构造函数)
- Bean references(Bean 引用)
- Array construction(数组构造)
- Inline lists(内联列表)
- Ternary operator(三目运算符)
- Variables(变量)
- User defined functions(用户定义的函数)
- Collection projection(集合投影)
- Collection selection(集合选择)
- Templated expressions(模板表达式)
SPEL基础
2.1、类型表达式
在SPEL中,使用 #{} 界定符被认为是SpEL表达式,可以使用相关变量、属性和方法等操作
2.2、类型表达式
T(type)用于获取一个类型的class对象。它的作用是让SpEL表达式在运行时获取指定的类型的Class对象,以便在表达式中可以使用该类的方法属性。
使用T(type)的语法格式为:T(package,ClassName),其中package是指类所在的包名,ClassName是类名,例如,要获取java.lang.String类的class对象,可以使用表达式T(java.lang.String)。
在SpEl中,同样可以使用T(type)来调用静态方法和属性,例如:T(java.lang.Math).PI表示获取Math类中的PI静态属性,T(java.lang.Math).random() 表示调用 Math 类中的 random() 静态方法。
需要注意的是,在使用T(type)操作符时,要确保指定的类型已经被加载,否则会抛出ClassNotFoundException异常。
2.3、SpEL 的 EvaluationContext 接口
在使用 SpEL 时,我们需要将表达式与 EvaluationContext 进行绑定,然后将表达式交给 SpEL 引擎执行。EvaluationContext 为 SpEL 引擎提供了上下文信息,使得 SpEL 引擎能够正确地解析表达式中的变量、函数等信息,从而求出表达式的结果
SimpleEvaluationContext 和 StandardEvaluationContext 都是 SpEL 提供的 EvaluationContext 的实现类
它们都提供了表达式求值所需的上下文信息,但是在具体实现方面略有不同
SimpleEvaluationContext 相对来说比较简单,它仅仅包含了变量解析器和类型转换器,不支持函数表达式。
而 StandardEvaluationContext 提供了更丰富的上下文信息,包括变量解析器、类型转换器和函数表达式等。同时, StandardEvaluationContext 还支持自定义函数和类加载器等高级功能。
通常情况下,如果只是简单的表达式求值,可以使用 SimpleEvaluationContext;如果需要使用函数表达式或自定义函数等高级功能,可以使用 StandardEvaluationContext
SPEL注入漏洞
1、漏洞注入原理
简单来说,当应用程序使用SpEL时,如果未能正确处理用户输入数据,攻击者可以在表达式中注入任意代码,并在应用程序的上下文执行它,进而造成命令执行等漏洞。
SpEL的EvaluationContext,其中StandardEvaluationContext 使用方法更加完整
因此触发SPEL注入漏洞的流程大致为:接收了用户的输入且未过滤等操作,将接收的参数使用StandardEvaluationContext来处理,并对表达式调用了getvalue()或setvalue()方法。
如上流程,后端接收了用户输入且为过滤,而攻击者精心构造攻击payload,即可实现命令执行等危险操作
2、代码示例
新建一个springboot项目,选择springweb
/*127.0.0.1:8080/spel/vul/? ex=T(java.lang.Runtime).getRuntime().exec("calc") */
package com.example.speldemo;import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class SpELdemo {/*127.0.0.1:8080/spel/vul/? ex=T(java.lang.Runtime).getRuntime().exec("calc") */@GetMapping("/spel/vuln")public String vul1(String ex){SpelExpressionParser spelExpressionParser = new SpelExpressionParser();//创建SPEL解析器//StandardEvaluationContext权限过大,可以执行任意代码,默认使用可以不指定EvaluationContext evaluationContext = new StandardEvaluationContext();//创建StandardEvaluationContext对象Expression exp = spelExpressionParser.parseExpression(ex);//解析用户传入的值return exp.getValue(evaluationContext).toString();//使用exp.getvalue获取值}
}
经过调试发现,创建解析器,创建运行环境,解析传入值,最后获取值,上面的每一步对于触发spel注入漏洞都很重要,少一半都可能无法触发。
而关键点在于parseExpression解析步骤,我们传入的T(java.lang.Runtime).getRuntime().exec("calc") ,被解析成了SPEL表达式,在获取值的时候触发了命令执行操作。
可以在return exp.getValue(evaluationContext).toString(); 处,打个断点进行 Debug。