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
接口有俩个实现类StandardEvaluationContext
和SimpleEvaluationContext
。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注入,但这里还是没有对传入的参数进行安全过滤,因此仍然可以进行拒绝服务攻击:
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