前面
在学习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
的例子。
加入依赖
<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");
}
}
此时已经可以验证参数了,不过参数不合法的话会直接返回 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 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-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
来源:https://xz.aliyun.com/ 感谢【j1ang 】
原文始发于微信公众号(衡阳信安):Java安全-深入BeanValidation的RCE漏洞