本帖最后由 jiangmingzi 于 2023-10-6 02:46 编辑
衡阳信安 2023-10-01 00:00 发表于湖南
前面在学习Java的表达式的时候,学习了Thymeleaf造成的模板注入漏洞,后续又看到了相似的注入问题,在参数验证的错误消息中。漏洞简单描述就是,用户控制器的Java Bean 的属性(来自于HTTP请求)被连接到 Bean Validation 的错误信息中,错误信息会被处理,其中的EL表达式会被执行,最后插入到违规信息并返回。 这里面可以提取到的重要信息: Bean Validation这是在 JSR(303) 提出来的,是Java定义的一套基于注解的数据规范验证。现在已经到了 JSR(380) 的Bean Validation 2.0 版。 使用他可以简单的在需要验证的类或者属性上加上对应的注解,就可以使用内置或者自定义验证器对Bean进行验证,优势就是只需要约束一次,不用在需要验证的所有接口处加入大量的if-else 判断,方便代码维护,简化代码量。 demo写一个springboot的bean 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})[ DISCUZ_CODE_3 ]quot;);
- 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");
- }
- }
复制代码
此时已经可以验证参数了,不过参数不合法的话会直接返回 400 和415 状态码,这并不友好,上面的自定义验证器添加的错误信息也没有意义了。 参数验证不通过抛出的异常是, 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 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
|