SpEL 表达式注入

gaorenyusi / 2024-09-13 / 原文

SpEL 表达式注入

spel 是什么

SpEL(Spring Expression Language),即Spring表达式语言,比JSP的EL更强大的一种表达式语言。特别是方法调用和基本的字符串模板功能。Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系,而SpEl可以方便快捷的对ApplicationContext中的Bean进行属性的装配和提取。

什么是EL,OGNL,SpEL?

  • EL:常用于jsp页面,简化jsp页面的操作
  • OGNL:struts中常见
  • SpEL:常用于Spring框架中

spel 依赖

<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-expression</artifactId>
      <version>5.0.8.RELEASE</version>
</dependency>

spel 语法

简单 demo

在Java语言中字符串是用""来包裹,但是在SpEL中使用''包裹字符串。尤其注意在parseExpression("'Hello SpEL'")方法中SpEL表达式需要用""来包裹,而不是所谓的#{},只有在注解和XML配置中才这样使用。以下是测试案例:

package org.example.controller;  
  
import org.springframework.expression.Expression;  
import org.springframework.expression.ExpressionParser;  
import org.springframework.expression.spel.standard.SpelExpressionParser;  
  
public class speltest {  
    public static void main(String[] args) {  
        ExpressionParser expressionParser = new SpelExpressionParser();  
        //1.创建expressionParser来解析表达式  
        Expression hello_world = expressionParser.parseExpression("'Hello SpEL'");  
        //2.放置表达式,表达式字符串由单引号''来包裹  
        Object value = hello_world.getValue();  
        System.out.println((String) value);  
        //3.通过Expression对象的getValue()来执行表达式  
    }  
}

在SpEL使用T()运算符会调用类作用域的方法和常量。例如,在SpEL中使用Java的Math类,我们可以像这样使用T()运算符:#{T(java.lang.Math)},该T()运算符的结果会返回一个java.lang.Math类对象。

定义Bean表达式支持

SpEL 表达式可以与基于 XML 或注释的配置元数据一起使用,以定义 BeanDefinitions。在这两种情况下,定义表达式的语法都是 。#{ <expression string> }

SpEL使用 #{...} 作为定界符,所有在大括号中的字符都将被认为是 SpEL表达式,我们可以在其中使用运算符,变量以及引用bean,属性和方法,这样的定界符只是用于xml和注解中,比如可以这样操作:

  • 引用其他对象: #{car}
  • 引用其他对象的属性:#{car.brand}
  • 调用其它方法 , 还可以链式操作:#{car.toString()}

XML中配置

<bean id="numberGuess" class="org.spring.samples.NumberGuess">
    <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>

    <!-- other properties -->
</bean>

注解中配置

public static class FieldValueTestBean

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

  public void setDefaultLocale(String defaultLocale)
  {
    this.defaultLocale = defaultLocale;
  }

  public String getDefaultLocale()
  {
    return this.defaultLocale;
  }

}

SpEL 支持有以下功能

Class expressions:类表达式

Method invocation:方法激活

Calling constructors:调用构造方法

Bean references:Bean表达式,如果已使用 Bean 解析程序配置了评估上下文,则可以使用 (@) 符号从表

Variables:变量表达式

User defined functions:定义方法

Templated expressions:模块化表达式

使用SpEL表达式

Class expressions

1.类型表达式

SpEL表达式可以访问(static)静态方法和静态属性,访问它们的时候需要用T()操作符进行声明,括号中需要包含类名的全限定名,也就是包名加上类名。唯一例外的是,SpEL内置了java.lang包下的类声明,也就是说java.lang.String可以通过T(String)访问,而不需要使用全限定名。T()它将返回一个 Class Object,然后可以再调用相应的静态方法或静态属性。这样说可能还不太清楚,横向对比一下

T(java.lang.Runtime)相当于Runtime.class相当于Class.forName("java.lang.Runtime") //显示的类型是一样的

如下面代码即可弹出计算机,

package org.example.controller;  
  
import org.springframework.expression.Expression;  
import org.springframework.expression.ExpressionParser;  
import org.springframework.expression.spel.standard.SpelExpressionParser;  
  
public class speltest {  
    public static void main(String[] args) {  
        ExpressionParser expressionParser = new SpelExpressionParser();  
        Expression hello_world = expressionParser.parseExpression("T(Runtime).getRuntime().exec(\"calc\")");  
        hello_world.getValue();  
  
    }  
}

这里要注意到 Expressionparser 声明了俩个方法,

两种都能进行调用

ExpressionParser expressionParser = new SpelExpressionParser();
//方式1
Expression exp = expressionParser.parseExpression("T(java.lang.Runtime).getRuntime().exec(\"calc\")");

//方式2
Expression exp = expressionParser.parseExpression("#{T(java.lang.Runtime).getRuntime().exec(\"calc\")}",new TemplateParserContext());

2.类实例化

使用new可以直接在SpEL中创建实例,需要创建实例的类要通过全限定名进行访问。如:

ExpressionParser parser = new SpelExpressionParser(); 
Expression exp = parser.parseExpression("new java.util.Date()"); 
Date value = (Date) exp.getValue(); 
System.out.println(value);

SpEL漏洞

EvaluationContext 实现类

实现类区别

EvaluationContext 接口有俩个实现类,分别是 StandardEvaluationContextSimpleEvaluationContext

其中 SimpleEvaluationContext 是一个安全的类,主要的漏洞修复就是靠它来实现的 (但这并不完美,因为我们还需要设置对变量值的过滤)。如果我们不进行设置的话 StandardEvaluationContext 是SpEL默认使用的 EvaluationContext, 我们可以看下SpEL提供的两个 EvaluationContext 的区别。

  • SimpleEvaluationContext - 针对不需要SpEL语言语法的全部范围并且应该受到有意限制的表达式类别,公开SpEL语言特性和配置选项的子集。
  • StandardEvaluationContext - 公开全套SpEL语言功能和配置选项。您可以使用它来指定默认的根对象并配置每个可用的评估相关策略。

总体来说SimpleEvaluationContext旨在仅支持SpEL语言语法的一个子集。它不包括 Java类型引用,构造函数和bean引用。所以说指定正确EvaluationContext,是防止SpEl表达式注入漏洞产生的首选,之前出现过相关的SpEL表达式注入漏洞,其修复方式就是使用SimpleEvaluationContext替代StandardEvaluationContext

demo演示

为了更清楚的展示两种EvaluationContext在处理SpEL功能上的差异,我们举一个例子。这里有一个包含SpEL表达式的恶意字符串:

String inject = "T(java.lang.Runtime).getRuntime().exec('calc.exe')";

StandardEvaluationContext:

StandardEvaluationContext std_c = new StandardEvaluationContext();

SimpleEvaluationContext:

EvaluationContext simple_c = SimpleEvaluationContext.forReadOnlyDataBinding().build();

然后放置表达式:

exp = parser.parseExpression(inject);

然后执行一下试试

java exp.getValue(std_c); //计算器将启动 
java exp.getValue(simple_c); //运行出现错误

看到报错显示

虽然说使用 SimpleEvaluationContext 有助于防止将SpEL注入,但这里还是没有对传入的参数进行安全过滤,因此仍然可以进行拒绝服务攻击(Dos)

SpEL漏洞寻找

为什么会出现SpEL漏洞?问题就在于使用StandardEvaluationContext来处理SpEL表达式,所以我们在勘察这类漏洞的时候就需要寻找在什么位置使用StandardEvaluationContext来处理表达式。

CVE-2018-1273 Spring Data Commons RCE

  • 手动发现
  • findsecbugs-cil插件
  • LGTM QL

手动发现

手动发现思路:ExpressionParser类----getValue()方法----EvaluationContext

看到这里的 context 为传入的 StandardEvaluationContext,然后获得 ExpressionState 后执命令