SpEL表达式注入

SpEL表达式.....学习,正好有几道java题练练手。web安全学习者应该学对我们重要部分的知识而不是全部系统的学习

1. 初始SpEL表达式

1.1 什么是SpEL?

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

什么是EL,OGNL,SpEL?

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

1.2 导入SpEL依赖

在 pom.xml 导入 maven 或是把"org.springframework.expression-3.0.5.RELEASE.jar"添加到类路径中

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

1.3 SpEL语法

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

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class Hello {
    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()来执行表达式
    }
}

Hello SpEL

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

1.3.2 定义Bean表达式支持

SpEL 表达式可以与基于 XML 或注释的配置元数据一起使用,以定义 BeanDefinitions。在这两种情况下,定义表达式的语法都是 。#{ <expression string> }。(有些博客说SpEL有三种使用方式,其实我感觉这样说不是很准确,按照官网的解释XML配置和注解配置都是为了服务Bean references功能的)

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;
  }

}

1.3.3 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表达式,如果已使用 Bean 解析程序配置了评估上下文,则可以使用 (@) 符号从表达式中查找 Bean。
Array construction
Inline lists
Ternary operator
Variables:变量表达式
User defined functions:定义方法
Collection projection
Collection selection
Templated expressions:模块化表达式

2. 使用SpEL表达式

心动不如行动.....直接使用学习Expression中使用

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") //显示的类型是一样的

三者都应该是类对象,但是这就有一个问题静态方法和属性的调用者都是类对象,那么Class.forName("java.lang.Runtime")Runtime.class得到的类对象为什么不可以调用getRuntime()这个静态方法呢?做个标记以后再解答

弹个计算器试试,运行以下java文件可以弹出计算器

package com;

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class Hello {
    public static void main(String[] args) {
        ExpressionParser expressionParser = new SpelExpressionParser();
        //1.创建expressionParser来解析表达式
        Expression hello_world = expressionParser.parseExpression("T(java.lang.Runtime).getRuntime().exec(\"calc\")");
        //2.放置表达式。这里也可以直接写成T(Runtime).getRuntime().exec("calc")
        hello_world.getValue();
        //3.通过Expression对象的getValue()来执行表达式
    }
}

这里要注意Expressionparser有声明了俩个方法:

所以说在写payload的时候记得要看清利用的是哪一个方法。这俩种对于SpEL的写法是不同的,需要格外注意

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);

3. SpEL漏洞

3.1 EvaluationContext实现类

实现类区别

EvaluationContext接口有俩个实现类StandardEvaluationContextSimpleEvaluationContextSimpleEvaluationContext是一个安全的类,主要的漏洞修复就是靠它来实现的 (但这并不完美,因为我们还需要设置对变量值的过滤)。如果我们不进行设置的话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注入,但这里还是没有对传入的参数进行安全过滤,因此仍然可以进行拒绝服务攻击:

curl -X POST http://localhost:8080/account -d "name['aaaaaaaaaaaaaaaaaaaaaaaa!'%20matches%20'%5E(a%2B)%2B%24']=test"

后来的修复已经增加了对输入参数的安全过滤处理。

3.2 SpEL漏洞寻找

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

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

3.2.1 手动发现

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

因为SpEL默认使用的是StandardEvaluation,所以直接默认发现

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class Hello {
    public static void main(String[] args) {
        ExpressionParser expressionParser = new SpelExpressionParser();
        //1.创建expressionParser来解析表达式
        Expression hello_world = expressionParser.parseExpression("T(java.lang.Runtime).getRuntime().exec(\"calc\")");
        //2.放置表达式,表达式字符串由单引号''来包裹
        Object value = hello_world.getValue();
        System.out.println((String) value);
        //3.通过Expression对象的getValue()来执行表达式
    }
}

进入在SpelExpressionParser#getValue()方法中,

在SpelExpressionParser这个类中的EvaluationContext都是通过getEvaluationContext()方法来获得的,如果传入一个EvaluationContext就使用传入的,如果没有就默认使用StandardEvaluation

参考链接

非常非常值得一看:http://rui0.cn/archives/1043

非常非常值得一看:https://cryin.github.io/blog/SpEL injection/

SpEL表达式总结:https://www.jianshu.com/p/e0b50053b5d3

Spring SpEL表达式语言 (biancheng.net)

posted @ 2022-07-06 23:55  B0T1eR  阅读(1946)  评论(0编辑  收藏  举报