Java安全-深入BeanValidation的RCE漏洞

前面

在学习Java的表达式的时候,学习了Thymeleaf造成的模板注入漏洞,后续又看到了相似的注入问题,在参数验证的错误消息中。漏洞简单描述就是,用户控制器的Java Bean 的属性(来自于HTTP请求)被连接到 Bean Validation 的错误信息中,错误信息会被处理,其中的EL表达式会被执行,最后插入到违规信息并返回。

这里面可以提取到的重要信息:

  • 使用 Java Bean Validation 验证
  • 验证用户可控的 Java Bean

  • Java Bean 的不合法属性回显到错误消息中(从回显处fuzz模板注入

Bean Validation

这是在 JSR(303) 提出来的,是Java定义的一套基于注解的数据规范验证。现在已经到了 JSR(380) 的Bean Validation 2.0 版。

使用他可以简单的在需要验证的类或者属性上加上对应的注解,就可以使用内置或者自定义验证器对Bean进行验证,优势就是只需要约束一次,不用在需要验证的所有接口处加入大量的if-else 判断,方便代码维护,简化代码量。

demo

写一个springbootbean validation 的例子。

加入依赖

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.1.2.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate.javax.persistence</groupId>
    <artifactId>hibernate-jpa-2.1-api</artifactId>
    <version>1.0.0.Final</version>
</dependency>

Hibernate Validator 是 Bean Validation 的实现,其中包含了

jakarta是java改名而来的。

创建一个 Input的 Java Bean

public class Input {

  @Id
  @GeneratedValue
  @NotNull
  @Null
  private Long id;

  @Min(1)
  @Max(10)
  @Column
  private int numberBetweenOneAndTen;

  @IpAddress
  @Column
  private String ipAddress;

  //setter getter ……

注意上面的@IpAddress 注解需要创建

@Target({ FIELD })
@Retention(RUNTIME)
@Constraint(validatedBy = IpAddressValidator.class)
@Documented
public @interface IpAddress {

  String message() default "";

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

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

}

@Constraint(validatedBy = IpAddressValidator.class) 用来指定由哪个类进行验证,创建它。

验证器需要继承自ConstraintValidator 接口,此接口使用了泛型,第一个参数是自定义的注解,第二个参数是校验的数据类型。

public class IpAddressValidator implements ConstraintValidator<IpAddress, String> {

  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    Pattern pattern = Pattern.compile("^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$");
    Matcher matcher = pattern.matcher(value);
    boolean isValid = true;
    String message = "";
    try {
      if (!matcher.matches()) {
        isValid = false;
        message = value+"不匹配";
      } else {
        for (int i = 1; i <= 4; i++) {
          int octet = Integer.valueOf(matcher.group(i));
          if (octet > 255) {
            isValid = false;
            message = value + "大于255";
          }
        }
      }
    } catch (Exception e) {
      isValid = false;
    }
    if(!isValid){
      context.disableDefaultConstraintViolation();
      context.buildConstraintViolationWithTemplate("invalid ip :" + message)
              .addConstraintViolation();
    }
    return isValid;

}

}

然后写一个Controller,加上@Valid 注解

@RestController
class ValidateRequestBodyController {

  @PostMapping("/validateBody")
  ResponseEntity<String> validateBody(@Valid @RequestBody Input input) {
    return ResponseEntity.ok("valid");
  }

}

此时已经可以验证参数了,不过参数不合法的话会直接返回 400415 状态码,这并不友好,上面的自定义验证器添加的错误信息也没有意义了。

参数验证不通过抛出的异常是,

MethodArgumentNotValidException

Spring 提供一个 @ExceptionHandler 注解,创建一个@ConrtrollerAdvice控制器接口,用于全局的异常处理,使用fastjson来回显json格式的错误信息。

@ControllerAdvice
class ErrorHandlingControllerAdvice {
  // 处理接口参数数据格式错误异常
  @ExceptionHandler(MethodArgumentNotValidException.class)
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ResponseBody
  public Object errorHandler(HttpServletRequest request, MethodArgumentNotValidException e) {
    Map<String,String> error = new HashMap<>();
    for (FieldError fieldError : e.getBindingResult().getFieldErrors()) {
      error.put(fieldError.getField(),fieldError.getDefaultMessage());
    }
    return JSON.toJSONString(error);
  }
}

可以发现 ipAddress的错误信息里直接把内容输出出来,尝试 EL表达式注入。

跟一下大概的逻辑,

验证失败后,写入自定义的错误信息,

然后在抛出带有错误信息的MethodArgumentNotValidException异常。

后面就是Springboot捕获异常,并使用特定的异常处理接口来处理。

问题还是出在验证过程中对于错误信息的处理,添加错误信息后,violatedConstraintValidatorContexts 不为空,

跟进,ValidationContext#addConstraintFailure

调用了 自身的interpolate 方法来插入消息,其中有两种主要的,一种是参数插值({})和表达式插值(${}),表达式插值会被执行Java 表达式语言引擎执行。

不断跟进中间的 interpolate 方法,直到org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator#interpolateMessage

如果存在 { 就会先处理 参数表达式插入,然后再处理EL 表达式。

造成EL表达式的注入。

interpolate:67, ElTermResolver (org.hibernate.validator.internal.engine.messageinterpolation)
interpolate:64, InterpolationTerm (org.hibernate.validator.internal.engine.messageinterpolation)
interpolate:159, ResourceBundleMessageInterpolator (org.hibernate.validator.messageinterpolation)
interpolateExpression:519, AbstractMessageInterpolator (org.hibernate.validator.messageinterpolation)
interpolateMessage:415, AbstractMessageInterpolator (org.hibernate.validator.messageinterpolation)
interpolate:355, AbstractMessageInterpolator (org.hibernate.validator.messageinterpolation)
interpolate:59, MessageSourceMessageInterpolator (org.springframework.boot.validation)
interpolate:51, LocaleContextMessageInterpolator (org.springframework.validation.beanvalidation)
interpolate:313, AbstractValidationContext (org.hibernate.validator.internal.engine.validationcontext)
addConstraintFailure:230, AbstractValidationContext (org.hibernate.validator.internal.engine.validationcontext)
validateConstraints:79, ConstraintTree (org.hibernate.validator.internal.engine.constraintvalidation)
doValidateConstraint:130, MetaConstraint (org.hibernate.validator.internal.metadata.core)
validateConstraint:123, MetaConstraint (org.hibernate.validator.internal.metadata.core)
validateMetaConstraint:555, ValidatorImpl (org.hibernate.validator.internal.engine)
validateConstraintsForSingleDefaultGroupElement:518, ValidatorImpl (org.hibernate.validator.internal.engine)
validateConstraintsForDefaultGroup:488, ValidatorImpl (org.hibernate.validator.internal.engine)
validateConstraintsForCurrentGroup:450, ValidatorImpl (org.hibernate.validator.internal.engine)
validateInContext:400, ValidatorImpl (org.hibernate.validator.internal.engine)
validate:172, ValidatorImpl (org.hibernate.validator.internal.engine)
validate:109, SpringValidatorAdapter (org.springframework.validation.beanvalidation)
validate:66, ValidatorAdapter (org.springframework.boot.autoconfigure.validation)
validate:895, DataBinder (org.springframework.validation)
validateIfApplicable:245, AbstractMessageConverterMethodArgumentResolver (org.springframework.web.servlet.mvc.method.annotation)
resolveArgument:139, RequestResponseBodyMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)
resolveArgument:121, HandlerMethodArgumentResolverComposite (org.springframework.web.method.support)
getMethodArgumentValues:179, InvocableHandlerMethod (org.springframework.web.method.support)
invokeForRequest:146, InvocableHandlerMethod (org.springframework.web.method.support)
invokeAndHandle:117, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
invokeHandlerMethod:895, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:808, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:87, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch
……

Nexus Repository Manager

CVE-2018-16621

影响版本:

Nexus Repository Manager OSS/Pro 3.x - 3.13

分析

org.sonatype.nexus.coreui.UserXO

存在自定义的验证注解,注解类型为org.sonatype.nexus.security.role.RolesExist

验证器为org.sonatype.nexus.security.role.RolesExistValidator

可以发现跟漏洞demo中的验证方法大致相同,可以在roles 参数处注入EL表达式。

所以存在验证roles参数的地方都可以触发漏洞。

修复

简单粗暴,

这个正则还防止了$${ 双写绕过的问题。

CVE-2020-10693 与 CVE-2020-10204

首先来说CVE-2020-10204 ,影响版本Nexus Repository Manager OSS/Pro 3.x -3.21.1,是对于上面的漏洞的绕过版本,使用了exp|$\\A{2*333}| 来绕过正则,他的修复是又加了一个replaceAll

为什么会出现这种绕过,是因为hibernate-validator6.0.15.final 之前的版本中,可以将错误的表达式,类似上面的exp,解析成正确的表达式执行,也就是CVE-2020-10693

https://in.relation.to/2020/05/07/hibernate-validator-615-6020-released/

分析

org.hibernate.validator.internal.engine.messageinterpolation.parser.TokenCollector#parse 方法将会对消息进行解析。

通过遍历错误信息,

当前的字符是$

调用org.hibernate.validator.internal.engine.messageinterpolation.parser.MessageState#handleELDesignator方法, interpolationTermType 属性值为EL

目前的状态变更为 ELState

当匹配到 \ 时,调用目前状态的currentParserState.handleEscapeCharacter

进入EscapedState 状态,接下来再匹配到{}$\ 以外字符的话,调用EscapedState#handleNonMetaCharacter方法,

将当前的字符给到currentToken,返回上一个状态 (ElState)。

后面的代码不贴了,直接说好了,后面到{ 的时候,ELState#handleBeginTerm

currentToken 进行一些结束操作然后写入tokenList,主要是定义其isEL属性。

然后给currentToken赋值为null,然后给他添加有 ${ 的value,然后,isELisParameter为true。

进入InterpolationTermState 状态,此状态就是不断添加字符,直到}

主要就是ELState#handleEscapeCharacter 方法,直接进入EscapedState 状态,使得$\再次遇到其他字符时,继续返回ELState状态,从而获取了合法的EL表达式。

https://github.com/hibernate/hibernate-validator/commit/6ae28a1bc8f7ccb208fa004bdb7c6da569be8a59#diff-3b58c78300599cd6be8467d4722d40a9912c7b256d708b291bce792290395f1b

参考

https://reflectoring.io/bean-validation-with-spring-boot/

https://beanvalidation.org/2.0/spec/

https://xz.aliyun.com/t/8319#toc-4

https://securitylab.github.com/research/bean-validation-RCE/

https://securitylab.github.com/advisories/GHSL-2020-020-hibernate-validator/

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖