Java代码审计之SpEL注入

一. SpEL 简介

SpEL,全称为 Spring 表达式语言(Spring Expression Language),是一个由 Spring 框架提供的表达式语言。它是一种基于字符串的表达式语言,可以在运行时对对象进行查询和操作。

SpEL 支持在XML和注解配置中使用,它可以在Spring框架的各种组件中使用,如Spring MVC 控制器、Spring Security 安全框架、Spring Data 数据访问框架等。它可以方便地访问对象的属性、调用对象的方法、进行数学运算、逻辑运算、正则表达式匹配等操作。

使用 SpEL 可以大大简化编程工作,使得配置和编写代码更加灵活和易于维护。例如,可以使用 SpEL 在XML 或注解中配置 Spring bean 的属性,或在 Spring MVC 控制器中动态地计算请求参数。

总而言之,SpEL 是 Spring 框架中一个非常有用的特性,它提供了一种灵活、简洁的方式来操作和查询对象。

http://itmyhome.com/spring/expressions.html 基础知识

SpEL 表达式语言支持以下功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Calling constructors(调用构造函数)
Method invocation(调用方法)
Class expressions(类表达式)
Bean references(Bean 引用)
Variables(变量)
Templated expressions(模板表达式)
Literal expressions(字面量表达式)
Boolean and relational operators(布尔和关系运算符)
Regular expressions(正则表达式)
Accessing properties, arrays, lists, maps(访问属性、数组、列表、映射)
Relational operators(关系运算符)
Assignment(赋值运算符)
Array construction(数组构造)
Inline lists(内联列表)
Ternary operator(三目运算符)
User defined functions(用户定义的函数)
Collection projection(集合投影)
Collection selection(集合选择)

1.2 表达式符号

在 SpEL 中,使用 #{<表达式>} 界定符被认为是 SpEL 表达式,可以使用相关变量、属性和方法等操作。

1.3 类型表达式 T(package.ClassName)

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 异常。

1.4 SpEL 用法

SpEL 的用法有三种形式:一是在注解中,二是在 XML 配置中,三是在代码中使用 Expression。

在注解中:

在 Spring 中,可以使用 @Value 注解将 SpEL 表达式应用于 Bean 属性、构造函数参数和集合元素等场景。@Value 注解可以将 SpEL 表达式注入到 Bean 的属性中,支持在表达式中引用其他 Bean、属性和环境变量等。

1
2
3
4
5
6
@Component
public class ExampleBean {
   @Value("#{systemProperties['user.name']}")
   private String name;
   // Getter and setter methods
}

在 XML 配置中:

在 Spring 中,可以在 XML 配置中使用 SpEL 表达式来配置 Bean。SpEL 表达式可以在 XML 配置中用 #{expression} 的形式引用。

1
2
3
<bean id="exampleBean" class="com.example.ExampleBean">
   <property name="name" value="#{systemProperties['user.name']}"/>
</bean>

在代码块中使用 Expression:

在 Spring 中,可以使用 SpEL 的 Expression 接口在代码块中使用 SpEL 表达式。使用 Expression 接口可以在运行时编译和评估表达式,也可以设置变量和函数等。

1
2
3
4
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("2 + 3");
Integer sum = (Integer) exp.getValue();
System.out.println(sum); // Output: 5

1.5 SpEL EvaluationContext 接口

在使用 SpEL 时,我们需要将表达式与 EvaluationContext 进行绑定,然后将表达式交给 SpEL 引擎执行。EvaluationContext 为 SpEL 引擎提供了上下文信息,使得 SpEL 引擎能够正确地解析表达式中的变量、函数等信息,从而求出表达式的结果。

SimpleEvaluationContext 和 StandardEvaluationContext 都是 SpEL 提供的 EvaluationContext 的实现类。

它们都提供了表达式求值所需的上下文信息,但是在具体实现方面略有不同。

SimpleEvaluationContext 相对来说比较简单,它仅仅包含了变量解析器和类型转换器,不支持函数表达式。

而 StandardEvaluationContext 提供了更丰富的上下文信息,包括变量解析器、类型转换器和函数表达式等。同时, StandardEvaluationContext 还支持自定义函数和类加载器等高级功能

通常情况下,如果只是简单的表达式求值,可以使用 SimpleEvaluationContext;如果需要使用函数表达式或自定义函数等高级功能,可以使用 StandardEvaluationContext。

二. SpEL 注入漏洞

简单来说,当应用程序使用 SpEL 时,如果未正确处理用户输入数据,攻击者可以在表达式中注入任意的代码,并在应用程序的上下文中执行它,进而造成命令执行等漏洞。在上面提到了 SpEL 的 EvaluationContext,其中 StandardEvaluationContext 使用方法更加完整。因此,触发 SpEL 的漏洞的流程大致为:

  1. 接收了用户的输入且未过滤等操作,将接收的参数使StandardEvaluationContext 去处理
  2. 并对表达式调用了 getValue() 或 setValue() 方法。如上流程,后端接收了用户输入且未过滤,而攻击者精心构造攻击 payload 即可实现命令执行等危险操作。
1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:8080/spel/vuln/?ex=T(java.lang.Runtime).getRuntime().exec("calc")
@GetMapping("/spel/vuln")
public String vul1(String ex) {

ExpressionParser parser = new SpelExpressionParser();
//StandardEvaluationContext权限过大,可以执行任意代码,默认使用可以不指定
EvaluationContext evaluationContext = new StandardEvaluationContext();
Expression exp = parser.parseExpression(ex);

return exp.getValue(evaluationContext).toString();
//return "test";
}

二. Java模板引擎与漏洞


Java代码审计之SpEL注入
http://example.com/2025/06/10/Java代码审计之SpEL注入/
作者
XCDH
发布于
2025年6月10日
许可协议