SpEL 安全加固宝典:变量注入防护全场景实战解析

SpEL 安全加固宝典:变量注入防护全场景实战解析

🚀 SpEL 安全加固宝典:变量注入防护全场景实战解析


摘要

  1. SpEL(Spring Expression Language)灵活强大,却易遭“注入”风险;
  2. 把用户输入当作“变量”传入,断绝表达式本体被篡改;
  3. 四大场景分析:硬编码、YAML 配置、数据库、注解;
  4. 深入解析:表达式从哪儿来、变量如何生效、为何安全;
  5. 拓展防护:SimpleEvaluationContext 能否一同保驾护航?

🧐 一、SpEL 注入风险简述

  • SpEL 是“表达式语言”,支持运行时动态计算,如: parser.parseExpression(“T(java.lang.Runtime).getRuntime().exec(‘calc’)”).getValue();
  • 如果将未经过滤的用户输入当作表达式本体,攻击者可执行任意 Java 代码,泄露数据、远程命令。

🔐 二、变量注入防护核心原理

  1. 表达式模板变量值 分离:
    • 模板String condition = "#user.level >= 5")固定、可控;
    • 变量context.setVariable("user", userInput))仅为“值”,不再被解析为表达式。
  2. 安全性:即使 userInput = "T(java.lang.Runtime)...“,也会被当作普通字符串,不会执行。
  3. 适用场景:权限控制、规则引擎、动态过滤、注解式安全等。

🛠️ 三、四大场景汇总

场景表达式来源说明
1. 硬编码直接写在 Java 代码里代码中可以明显看的硬编码的
表达式模板内容。
2. YAML 配置application.yml通过 @ConfigurationProperties+
Controller 实时传参。
3. 数据库表中字段 expressionController 从 DB 读出规则,再
注入网络请求出入的变量参数。
4. 注解@PreAuthorize注解式声明表达式模板。

3.1 硬编码示例

 @RestController
 public class HardcodedSpELController {
 ​
     @PostMapping("/eval-hardcoded")
     public Boolean eval(@RequestBody User user) {
         // 1. 解析器与上下文
         ExpressionParser parser = new SpelExpressionParser();
         StandardEvaluationContext ctx = new StandardEvaluationContext();
         // 2. 获取网络请求的变量User
         ctx.setVariable("user", user);
         // 3. 硬编码的表达式模板
         String condition = "#user.level >= 5";
         // 4. 执行并返回结果
         return parser.parseExpression(condition)
                      .getValue(ctx, Boolean.class);
     }
 ​
     public static class User {
         private String role;
         private int level;
         public User() {}
         public String getRole() { return role; }
         public void setRole(String role) { this.role = role; }
         public int getLevel() { return level; }
         public void setLevel(int level) { this.level = level; }
     }
 }

3.2 YAML 配置示例

 # application.yml
 access:
   rule: "#user.role == 'admin'"
 
 @Component
 @ConfigurationProperties("access")
 class AccessRuleConfig {
     private String rule;
     public String getRule() { return rule; }
     public void setRule(String rule) { this.rule = rule; }
 }
 ​
 @RestController
 public class YamlSpELController {
 ​
     @Autowired
     private AccessRuleConfig config;
 ​
     @PostMapping("/eval-yaml")
     public Boolean eval(@RequestBody User user) {
         // 1. 注入变量
         StandardEvaluationContext ctx = new StandardEvaluationContext();
         ctx.setVariable("user", user);
         // 2. 解析 YAML 中的表达式模板
         String condition = config.getRule();
         return new SpelExpressionParser()
             .parseExpression(condition)
             .getValue(ctx, Boolean.class);
     }
 ​
     public static class User {
         private String role;
         private int level;
         public User() {}
         public String getRole() { return role; }
         public void setRole(String role) { this.role = role; }
         public int getLevel() { return level; }
         public void setLevel(int level) { this.level = level; }
     }
 }

3.3 数据库读取示例

 INSERT INTO access_rule(resource,expression) VALUES('/vip','#user.level>=10');
 
 @RestController
 public class DbSpELController {
 ​
     @Autowired
     private AccessRuleDao dao;
 ​
     @PostMapping("/eval-db")
     public Boolean eval(@RequestParam String resource,
                         @RequestBody User user) {
         // 1. 从数据库读取表达式模板
         String expr = dao.getRuleByResource(resource).getExpression();
         // 2. 注入网络请求的 user
         StandardEvaluationContext ctx = new StandardEvaluationContext();
         ctx.setVariable("user", user);
         // 3. 执行并返回
         return new SpelExpressionParser()
             .parseExpression(expr)
             .getValue(ctx, Boolean.class);
     }
 }

3.4 注解式示例

 @EnableGlobalMethodSecurity(prePostEnabled=true)
 @Configuration
 class SecurityConfig { /* ... */ }
 ​
 @RestController
 public class AnnotatedSpELController {
 ​
     @GetMapping("/admin-area")
     @PreAuthorize("#user.role == 'admin'")
     public String admin(@RequestBody User user) {
         // 如果通过,才会执行此方法体
         return "欢迎管理员:" + user.getRole();
     }
 ​
     public static class User {
         private String role;
         private int level;
         public User() {}
         public String getRole() { return role; }
         public void setRole(String role) { this.role = role; }
         public int getLevel() { return level; }
         public void setLevel(int level) { this.level = level; }
     }
 }

Spring Security 会在调用前,把网络请求中的 user 对象注入到表达式上下文。


🤔 四、核心疑问全解析

  1. 表达式从哪来?
    • 就是字符串模板,源自硬编码、配置文件、数据库、注解等。
  2. 变量值有啥用?
    • 为“模板”提供运行时数据;不参与模板构造,彻底切断注入通路。
  3. 为何能防注入?
    • 用户输入 只做值,不允许出现在表达式的语法层面。
  4. 何时不用 SpEL?
    • 逻辑固定、无需动态配置时,可直接用 Java 代码。

⚙️ 五、SimpleEvaluationContext 防护

5.1 什么是 SimpleEvaluationContext

  • 简化版 EvaluationContext,只支持读写属性禁用方法调用类型引用等高级功能。
  • 适合“只读数据绑定”场景,进一步缩小攻击面。

5.2 示例代码

 @RestController
 public class SpELController {
 ​
     @PostMapping("/evaluate")
     public Boolean evaluateAccess(@RequestBody User user) {
         // 1. 构建只读上下文,禁止方法调用 & 类型访问
         SimpleEvaluationContext ctx = SimpleEvaluationContext
             .forReadOnlyDataBinding()
             .build();
 ​
         // 2. 真实网络请求反序列化得到的 user
         ctx.setVariable("user", user);
 ​
         // 3. 表达式模板
         String condition = "#user.level >= 5";
 ​
         // 4. 解析并执行
         return new SpelExpressionParser()
             .parseExpression(condition)
             .getValue(ctx, Boolean.class);
     }
 ​
     public static class User {
         private String role;
         private int level;
         public User() {}
         public String getRole() { return role; }
         public void setRole(String role) { this.role = role; }
         public int getLevel() { return level; }
         public void setLevel(int level) { this.level = level; }
     }
 }

5.3 防护效果对比

功能StandardEvaluationContextSimpleEvaluationContext
属性读取
方法调用
类型引用
自定义函数
注入面较小(依赖变量)极小(连本地方法都禁了)

结论

  • 变量防护 是第一道屏障;
  • SimpleEvaluationContext 能当第二道盾牌,彻底锁死方法调用与类型访问,适合最严格的只绑定场景。

🎉 六、全文小结

  • SpEL 强大又危险,只要表达式与变量分离,即可杜绝注入;
  • 四大场景(硬编码/YAML/数据库/注解)覆盖所有常见用法;
  • 核心套路:模板固定、变量带值、安全解析;
  • 防护升级SimpleEvaluationContext 禁止方法 & 类型,更严苛。

提示

  • 项目常见表达式来源(配置、数据库);
  • 核心关注:用户输入是否都作为变量传入,不参与表达式拼接;
    •  parseExpression(condition) //内容是否可控
  • 若为 Web 场景或需极致安全,推荐使用 SimpleEvaluationContext

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

请登录后发表评论

    暂无评论内容