安全矩阵

 找回密码
 立即注册
搜索
查看: 866|回复: 0

Java安全-深入BeanValidation的RCE漏洞

[复制链接]

102

主题

102

帖子

330

积分

中级会员

Rank: 3Rank: 3

积分
330
发表于 2023-10-6 02:46:49 | 显示全部楼层 |阅读模式
本帖最后由 jiangmingzi 于 2023-10-6 02:46 编辑

衡阳信安 2023-10-01 00:00 发表于湖南

前面
在学习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
写一个springboot的bean validation 的例子。
加入依赖
  1. <dependency>
  2.     <groupId>org.hibernate.validator</groupId>
  3.     <artifactId>hibernate-validator</artifactId>
  4.     <version>6.1.2.Final</version>
  5. </dependency>
  6. <dependency>
  7.     <groupId>org.hibernate.javax.persistence</groupId>
  8.     <artifactId>hibernate-jpa-2.1-api</artifactId>
  9.     <version>1.0.0.Final</version>
  10. </dependency>
复制代码

Hibernate Validator 是 Bean Validation 的实现,其中包含了
jakarta是java改名而来的。
创建一个 Input的 Java Bean
  1. public class Input {

  2.   @Id
  3.   @GeneratedValue
  4.   @NotNull
  5.   @Null
  6.   private Long id;

  7.   @Min(1)
  8.   @Max(10)
  9.   @Column
  10.   private int numberBetweenOneAndTen;

  11.   @IpAddress
  12.   @Column
  13.   private String ipAddress;

  14.   //setter getter ……
复制代码

注意上面的@IpAddress 注解需要创建
  1. @Target({ FIELD })
  2. @Retention(RUNTIME)
  3. @Constraint(validatedBy = IpAddressValidator.class)
  4. @Documented
  5. public @interface IpAddress {

  6.   String message() default "";

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

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

  9. }
复制代码

@Constraint(validatedBy = IpAddressValidator.class) 用来指定由哪个类进行验证,创建它。
验证器需要继承自ConstraintValidator 接口,此接口使用了泛型,第一个参数是自定义的注解,第二个参数是校验的数据类型。
  1. public class IpAddressValidator implements ConstraintValidator<IpAddress, String> {

  2.   @Override
  3.   public boolean isValid(String value, ConstraintValidatorContext context) {
  4.     Pattern pattern = Pattern.compile("^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})[        DISCUZ_CODE_3        ]quot;);
  5.     Matcher matcher = pattern.matcher(value);
  6.     boolean isValid = true;
  7.     String message = "";
  8.     try {
  9.       if (!matcher.matches()) {
  10.         isValid = false;
  11.         message = value+"不匹配";
  12.       } else {
  13.         for (int i = 1; i <= 4; i++) {
  14.           int octet = Integer.valueOf(matcher.group(i));
  15.           if (octet > 255) {
  16.             isValid = false;
  17.             message = value + "大于255";
  18.           }
  19.         }
  20.       }
  21.     } catch (Exception e) {
  22.       isValid = false;
  23.     }
  24.     if(!isValid){
  25.       context.disableDefaultConstraintViolation();
  26.       context.buildConstraintViolationWithTemplate("invalid ip :" + message)
  27.               .addConstraintViolation();
  28.     }
  29.     return isValid;

  30. }

  31. }
复制代码

然后写一个Controller,加上@Valid 注解
  1. @RestController
  2. class ValidateRequestBodyController {

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

  7. }
复制代码

此时已经可以验证参数了,不过参数不合法的话会直接返回 400 和415 状态码,这并不友好,上面的自定义验证器添加的错误信息也没有意义了。
参数验证不通过抛出的异常是,
MethodArgumentNotValidException,
Spring 提供一个 @ExceptionHandler 注解,创建一个@ConrtrollerAdvice控制器接口,用于全局的异常处理,使用fastjson来回显json格式的错误信息。
  1. @ControllerAdvice
  2. class ErrorHandlingControllerAdvice {
  3.   // 处理接口参数数据格式错误异常
  4.   @ExceptionHandler(MethodArgumentNotValidException.class)
  5.   @ResponseStatus(HttpStatus.BAD_REQUEST)
  6.   @ResponseBody
  7.   public Object errorHandler(HttpServletRequest request, MethodArgumentNotValidException e) {
  8.     Map<String,String> error = new HashMap<>();
  9.     for (FieldError fieldError : e.getBindingResult().getFieldErrors()) {
  10.       error.put(fieldError.getField(),fieldError.getDefaultMessage());
  11.     }
  12.     return JSON.toJSONString(error);
  13.   }
  14. }
复制代码

可以发现 ipAddress的错误信息里直接把内容输出出来,尝试 EL表达式注入。
跟一下大概的逻辑,
验证失败后,写入自定义的错误信息,
然后在抛出带有错误信息的MethodArgumentNotValidException异常。
后面就是Springboot捕获异常,并使用特定的异常处理接口来处理。
问题还是出在验证过程中对于错误信息的处理,添加错误信息后,violatedConstraintValidatorContexts 不为空,
跟进,ValidationContext#addConstraintFailure
调用了 自身的interpolate 方法来插入消息,其中有两种主要的,一种是参数插值({})和表达式插值(${}),表达式插值会被执行Java 表达式语言引擎执行。
不断跟进中间的 interpolate 方法,直到org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator#interpolateMessage
如果存在 { 就会先处理 参数表达式插入,然后再处理EL 表达式。
造成EL表达式的注入。
  1. interpolate:67, ElTermResolver (org.hibernate.validator.internal.engine.messageinterpolation)
  2. interpolate:64, InterpolationTerm (org.hibernate.validator.internal.engine.messageinterpolation)
  3. interpolate:159, ResourceBundleMessageInterpolator (org.hibernate.validator.messageinterpolation)
  4. interpolateExpression:519, AbstractMessageInterpolator (org.hibernate.validator.messageinterpolation)
  5. interpolateMessage:415, AbstractMessageInterpolator (org.hibernate.validator.messageinterpolation)
  6. interpolate:355, AbstractMessageInterpolator (org.hibernate.validator.messageinterpolation)
  7. interpolate:59, MessageSourceMessageInterpolator (org.springframework.boot.validation)
  8. interpolate:51, LocaleContextMessageInterpolator (org.springframework.validation.beanvalidation)
  9. interpolate:313, AbstractValidationContext (org.hibernate.validator.internal.engine.validationcontext)
  10. addConstraintFailure:230, AbstractValidationContext (org.hibernate.validator.internal.engine.validationcontext)
  11. validateConstraints:79, ConstraintTree (org.hibernate.validator.internal.engine.constraintvalidation)
  12. doValidateConstraint:130, MetaConstraint (org.hibernate.validator.internal.metadata.core)
  13. validateConstraint:123, MetaConstraint (org.hibernate.validator.internal.metadata.core)
  14. validateMetaConstraint:555, ValidatorImpl (org.hibernate.validator.internal.engine)
  15. validateConstraintsForSingleDefaultGroupElement:518, ValidatorImpl (org.hibernate.validator.internal.engine)
  16. validateConstraintsForDefaultGroup:488, ValidatorImpl (org.hibernate.validator.internal.engine)
  17. validateConstraintsForCurrentGroup:450, ValidatorImpl (org.hibernate.validator.internal.engine)
  18. validateInContext:400, ValidatorImpl (org.hibernate.validator.internal.engine)
  19. validate:172, ValidatorImpl (org.hibernate.validator.internal.engine)
  20. validate:109, SpringValidatorAdapter (org.springframework.validation.beanvalidation)
  21. validate:66, ValidatorAdapter (org.springframework.boot.autoconfigure.validation)
  22. validate:895, DataBinder (org.springframework.validation)
  23. validateIfApplicable:245, AbstractMessageConverterMethodArgumentResolver (org.springframework.web.servlet.mvc.method.annotation)
  24. resolveArgument:139, RequestResponseBodyMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)
  25. resolveArgument:121, HandlerMethodArgumentResolverComposite (org.springframework.web.method.support)
  26. getMethodArgumentValues:179, InvocableHandlerMethod (org.springframework.web.method.support)
  27. invokeForRequest:146, InvocableHandlerMethod (org.springframework.web.method.support)
  28. invokeAndHandle:117, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
  29. invokeHandlerMethod:895, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
  30. handleInternal:808, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
  31. handle:87, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
  32. doDispatch
  33. ……
复制代码

Nexus Repository ManagerCVE-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-validator 在6.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,然后,isEL,isParameter为true。
进入InterpolationTermState 状态,此状态就是不断添加字符,直到}
主要就是ELState#handleEscapeCharacter 方法,直接进入EscapedState 状态,使得$\再次遇到其他字符时,继续返回ELState状态,从而获取了合法的EL表达式。
https://github.com/hibernate/hibernate-validator/commit/6ae28a1bc8f7ccb208fa004bdb7c6da569be8a59#diff-3b58c78300599cd6be8467d4722d40a9912c7b256d708b291bce792290395f1b
[color=rgba(0, 0, 0, 0.9)]来源:https://xz.aliyun.com/  感谢【j1ang 】

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-28 05:41 , Processed in 0.013908 second(s), 19 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表