SPEL 注入 使用 SPEL 的安全问题

SPEL Injection Security concerns for using SPEL

提问人:AdityaK01 提问时间:9/18/2023 更新时间:9/22/2023 访问量:117

问:

我们计划在基于 SaaS 的产品中使用 SPEL 来执行条件表达式。我的要求是使用评估条件,用户可以使用变量、常量和标准函数编写条件表达式。到目前为止,我使用 SpelExpressionParser() 解析器来解析表达式,然后使用 StandardEvaluationContext() 创建上下文,然后使用 expression evaluate 来计算表达式。

    ExpressionParser parser = new SpelExpressionParser();
    Expression exp = parser.parseExpression("(#Tag.matches('52.*') && #Name.toUpperCase().matches('DEF.*') ");
    EvaluationContext context = new StandardEvaluationContext();
    context.setVariable("Tag", "57345");
    context.setVariable("Name", "defg");
    Boolean result = (Boolean) exp.getValue(context);

这适用于我的所有要求,但我的安全团队对使用 StandardEvaluationContext() 有顾虑。StandardEvaluationContext() 可用于 SpEL 注入,并可用于使用 Java RMI 的远程方法调用。以下是一些安全问题:

SpEL 注入攻击:https://0xn3va.gitbook.io/cheat-sheets/framework/spring/spel-injection

OSWASP 关于 EL 注入攻击的文章:https://owasp.org/www-community/vulnerabilities/Expression_Language_Injection

我们被要求使用 SimpleEvaluationContext(),在那里我看到了我不能使用标准 java 函数和用户定义函数的限制(可能我不清楚是否有办法使用它)。

我计划在我的代码中使用最新的 2.7.x 版本的 spring 框架和 Java 11。

有人可以指导我了解:

  1. 使用 StandardEvaluationContext() 的更安全方法,我可以禁用某些 Java 函数,如 RMI 和系统函数,以便我可以安全地在我们的产品中使用?

  2. 如果 SimpleEvaluationContext() 是要走的路,那么有人可以帮我使用标准函数和用户定义的函数吗?

安全 代码注入 弹簧-EL

评论

0赞 Andrei Lisa 9/18/2023
这个问题的答案呢,也有一些文档参考。
0赞 AdityaK01 9/21/2023
谢谢@AndreiLisa。这确实有助于理解如何利用 StandardEvaluationContext,但使用 SimpleEvaluationContext 并不能真正满足我的要求,因为它不允许我使用方法/函数。
0赞 Andrei Lisa 9/21/2023
如果您要提供更多上下文(如何处理用户输入),我可能会帮助您找到另一种替代方案。
0赞 AdityaK01 9/22/2023
我的要求是用户应该能够输入条件表达式,并且根据条件表达式的计算结果,我们将对每一行数据执行某些操作。一个非常简单的例子可能是:比如说,我们有包含姓名、年龄、电话、电子邮件和促销字段的数据集。现在,如果用户输入类似 ((Age > 30) & & (Age < 50)) 的条件,并且条件的评估结果为 true,则我们将“促销”列更新为 true,否则将其更新为 false。当然,条件和动作可能更复杂,并且会有所不同。希望,这让事情变得更清楚了。
0赞 Andrei Lisa 9/22/2023
嘿,我添加了一个答案,你能检查一下这种方法并告诉我它是否符合你的要求吗?此外,我认为这是基于问题和评论描述的好方法。

答:

0赞 Andrei Lisa 9/22/2023 #1

在了解一些细节后,您可以更改方法并避免在这种情况下使用,而是使用 Java Bean Validation 并创建一个自定义方法。SPEL

像这样的东西:

将注释声明为ValidatorValuesTargetTYPE

@Documented
@Constraint(validatedBy = ValidatorValuesImpl.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidatorValues {

  String message() default "All good";

  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};

}

创建一个 impl,您将在其中将条件检查到方法中,这里作为示例,我从注释中放置了您的案例,顺便说一句,该方法总是返回 true,因为我们将其用作isValidvalue generatorconstraint validator

public class ValidatorValuesImpl implements ConstraintValidator<ValidatorValues, Dataset> {

  @Override
  public void initialize(ValidatorValues constraintAnnotation) {
    ConstraintValidator.super.initialize(constraintAnnotation);
  }

  @Override
  public boolean isValid(Dataset value, ConstraintValidatorContext context) {
    value.setPromotion((value.getAge() > 30) && (value.getAge() < 50));
    //.... others conditions

    return true;
  }
} 

数据集也来自注释示例,这里我设置了 at 字段,因为它是根据用户输入动态设置的。@JsonIgnorepromotion

@Data
@AllArgsConstructor
@NoArgsConstructor
@ValidatorValues
public class Dataset {

  private String name;

  private Integer age;

  private String phone;

  private String email;

  @JsonIgnore
  private Boolean promotion;
} 

还有当请求到达后端时将使用它的控制器层:

@RestController
@RequestMapping("/endpoint")
public class DataSetController {

  private final Validator validator;

  public DataSetController(Validator validator) {
    this.validator = validator;
  }

  @PostMapping("/test")
  ResponseEntity<String> createDataset(@RequestBody @Valid Dataset eventRequestBody) {
    validator.validate(eventRequestBody);

    return ResponseEntity.ok("promotion: " + eventRequestBody.getPromotion().toString());
  }
}

一些测试用例 postman 请求/响应:

  1. 如果年龄未通过验证:应返回促销:false

enter image description here

  1. 如果年龄通过验证:应返回促销:true

enter image description here