🌱 什么是 SpEL 注入?
SpEL 全称是 Spring Expression Language(Spring 表达式语言),是 Spring 框架中的一种表达式语法,用来在 Java 代码或配置中动态计算值。
SpEL 注入就是攻击者通过构造恶意表达式,让服务器“帮忙”执行本不该执行的操作,比如:
- 执行系统命令(远程控制服务器)
- 读取敏感数据(如配置文件、环境变量)
- 获取后台 Java 对象,甚至控制整个程序
你可以把它理解成一个“会执行代码的数学计算器”,本来只该算个 1+1
,结果你能让它去执行 删库跑路
的操作。
💥 SpEL 注入产生过程
SpEL 是“动态的”,像个计算器:
parser.parseExpression("1 + 1").getValue(); // 返回 2
🌟风险来了: 当程序像下面这样,直接使用用户输入作为表达式内容:
String userInput = request.getParameter("exp");
parser.parseExpression(userInput).getValue(); // ❌危险!用户控制表达式
如果攻击者输入:
T(java.lang.Runtime).getRuntime().exec("calc")
就等于执行了系统命令,打开计算器(或在 Linux 中执行任意命令)。
🧬 哪些代码会导致 SpEL 注入?
❌ 用户输入直接作为表达式
@GetMapping("/spel")
public String test(@RequestParam String exp) {
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(exp);
return expression.getValue().toString(); // 用户输入被执行!
}
✅ 安全写法
- 不直接接收表达式参数,将参数作为变量注入
- 只允许白名单表达式或固定模板,禁止调用
T()
类型访问器 - 使用沙箱限制 SpEL 功能
❌ 拼接用户输入
@Value("#{user.id = ${user.input}}") // 等价于:@Value("#{T(...).exec(...)})")
private String dangerousValue;
@GetMapping("/eval")
public String eval(@RequestParam String expr) {
// ⚠️ 直接拼接用户参数做最终表达式
String spel = "#{user.id = " + expr + "}";
ExpressionParser parser = new SpelExpressionParser();
return String.valueOf(parser.parseExpression(spel).getValue());
}
@PreAuthorize("#{T(com.example.RuleEngine).evaluate('" + rule + "', #cmd)}")
@GetMapping("/run")
public String run(@RequestParam String cmd) {
return "executed";
}
✅ 安全写法
- 不采用拼接,固定模板,然后变量注入参数
- 注意变量位置,涉及命令执行的,执行位置不可作为变量或做严格白名单控制
❌ 动态注解
// ⚠️ 动态格式注解
@PreAuthorize("#{T(com.example.SpelHelper).getExpressionFromDb()}")
public class SpelHelper {
public static String getExpressionFromDb() {
// ⚠️ 假设这里返回 "#user.role == 'admin'",但也可能被恶意改为:
return "T(java.lang.Runtime).getRuntime().exec('calc')";
}
}
✅ 安全写法
- 注解采用固定表达式形式
- 注解的可控参数,采用变量注入
@PreAuthorize("#{userService.canAccess(#id)}")
变量注入@PreAuthorize("#user.role == 'admin'")
固定模板+变量注入
注解形式下SpEL执行原理分析👉 注解中的 SpEL 表达式执行原理与注入风险详解
🧭 源码审计关键词清单
关键词 | 风险点说明 |
---|---|
SpelExpressionParser | 表达式解析器,是入口函数 |
ExpressionParser.parseExpression | 常用于解析表达式 |
Expression.getValue() | 执行表达式的核心位置 |
setVariable , setRootObject | 将用户对象或变量注入上下文 |
parseExpression() | 检查是否使用了用户输入 |
getValue() | 表达式是否被执行 |
StandardEvaluationContext | 上下文构造,可注入变量或对象,检查是否可控 |
@Value @PreAuthorize 注解 | 属性注入是否使用了拼接或动态注解 |
eval , exec , T(...) | 系统命令执行的参数是否可控 |
📌 小技巧: 源码中可搜索关键字:SpelExpressionParser
、parseExpression
、getValue
、T(
,看看是否有用户输入流向这些位置。
🛡️ 审计建议
🧩 要点 | 🔍 检查内容 |
---|---|
用户输入流向 | 检查用户输入是否流入 parseExpression() 、getValue() 、@Value 注解等表达式处理逻辑中。 |
表达式执行入口 | 是否存在 SpelExpressionParser.parseExpression() 、Expression.getValue() 等执行表达式的代码。 |
类型访问调用 | 是否出现 T(...) 调用(如访问 java.lang.Runtime 、System 等危险类)。 |
变量注入上下文 | 查看是否使用了StandardEvaluationContext.setVariable() 、setRootObject() 并注入用户可控对象。 |
注解中的 SpEL | 搜索 @Value , @PreAuthorize , @Scheduled 等注解,看是否有动态拼接 或用户参与的表达式。 |
黑名单/白名单校验 | 是否有对 SpEL 表达式做了内容检查、 执行限制、类访问限制 (沙箱限制)。 |
动态规则/脚本系统 | 检查是否有“规则引擎”、“表达式引擎” 等模块支持用户自定义 表达式,是否做了安全控制。 |
🧪 危险表达式示例
// 读取文件内容
T(java.nio.file.Files).readAllBytes(T(java.nio.file.Paths).get('/etc/passwd'))
// 执行系统命令
T(java.lang.Runtime).getRuntime().exec('calc')
// 加载类或反射操作
T(java.lang.Class).forName('java.lang.Runtime')
🛡️ 建议
- 在进行代码审计时,可重点关注
@PreAuthorize
注解、动态模板解析、前端传入表达式等场景。 - 若目标为 Spring Boot 应用,可 fuzz 接口参数是否会进入 SpEL 解析流程。
- 强烈建议结合
Spring Boot Actuator
与Spring Expression
功能点联动分析,提高利用成功率。
黑盒渗透测试具体分析👉 别再只测 1+1!SpEL 注入黑盒实战还有这些高阶姿势
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
暂无评论内容