SpEL 注入全网最通俗解释!看完源码审计轻松拿下!

SpEL 注入全网最通俗解释!看完源码审计轻松拿下!

🌱 什么是 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(...)系统命令执行的参数是否可控

📌 小技巧: 源码中可搜索关键字:SpelExpressionParserparseExpressiongetValueT(,看看是否有用户输入流向这些位置。


🛡️ 审计建议

🧩 要点🔍 检查内容
用户输入流向检查用户输入是否流入 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 ActuatorSpring Expression 功能点联动分析,提高利用成功率。

黑盒渗透测试具体分析👉 别再只测 1+1!SpEL 注入黑盒实战还有这些高阶姿势


© 版权声明
THE END
喜欢就支持一下吧
点赞5赞赏 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容