BeanValidation RCE 漏洞复现分析

admin 2022年2月15日19:54:09评论604 views字数 7402阅读24分40秒阅读模式


前言


最近复现分析了CVE-2018-16621、CVE-2020-10204,以此文章做篇记录,主要是关于Nexus漏洞的一个调试分析,学习Bean Validation漏洞挖掘的思路和技巧。


BeanValidation RCE 漏洞复现分析



Bean Validation


Bean Validation 为 JavaBean 验证定义了相应的元数据模型和 API,是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。在java应用程序中,通过使用 Bean Validation 或是你自己定义的 constraint,例如 @NotNull, @Max, @ZipCode, 就可以确保数据模型(JavaBean)的正确性。

而Hibernate Validator 是 Bean Validation 的参考实现,Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。参考http://www.hibernate.org/subprojects/validator.html

下面列一些常见的constraint

  • @NotNull:被注释的元素必须不为 null

  • @NotEmpty:被注释的元素不能为空。

  • @NotBlank:表示字符串字段不能是空字符串(即它必须至少有一个字符)。

  • @Min@Max:表示数值字段仅在其值高于或低于某个值时才有效。

  • @Pattern:说一个字符串字段只有在匹配某个正则表达式时才有效。

  • @Email表示字符串字段必须是有效的电子邮件地址。

举一个类示例:

class Customer {
 @Email
 private String email

 @NotBlank
 private String name;
   
 @ListNotHasNull  //自定义注解
 private List<String> info;
 // ...
}

为了验证一个对象是否有效,Bean Validation会将它传递给一个Validator来检查是否满足要求,不满足则抛出异常,例如:

Set<ConstraintViolation<Input>> violations = validator.validate(customer);
if (!violations.isEmpty()) {
 throw new ConstraintViolationException(violations);
}

但在spring中我们可以直接使用@Validated@Valid注解进行验证,@Validated是一个类级别的注解,用它来告诉Spring 验证传递给被注解类的方法的参数,

@Valid注解是放在方法参数和字段上,告诉 Spring 我们想要验证的对象,例如我们要对Customer对象进行验证,结合使用示例如下:

@Service
@Validated
class ValidatingService{
   void validateInput(@Valid Customer customer){
     // do something
  }
}

而Validator可以使用内置的也可以使用自定义的验证器对Bean进行验证,例如上面举例的Customer类中的自定义注解@ListNotHasNull中需要指定由哪个类进行验证

@Target({ FIELD })
@Retention(RUNTIME)
@Constraint(validatedBy = ListNotHasNullValidatorImpl.class)
@Documented
public @interface ListNotHasNull {
 String message() default "";
 Class<?>[] groups() default { };
 Class<? extends Payload>[] payload() default { };

}

@Constraint指向接口实现的注解ConstraintValidator,像这里指定了约束条件ListNotHasNullValidatorImpl类验证器进行验证,用于判断List集合中是否含有null元素,值得的注意的是验证器需要继承自ConstraintValidator 接口,第一个参数是自定义的注解,第二个参数是校验的数据类型

public class ListNotHasNullValidatorImpl implements ConstraintValidator<ListNotHasNullList> {
   private int value;

   @Override
   public void initialize(ListNotHasNull constraintAnnotation) {
       //传入value 值,可以在校验中使用
       this.value = constraintAnnotation.value();
  }
   public boolean isValid(List listConstraintValidatorContext context) {
       for (Object object : list) {
           if (object == null) {
               //如果List集合中含有Null元素,校验失败
               return false;
          }
      }
       return true;
  }
}

了解了Bean Validation的基本使用之后我们就可以开始Nexus Repository Manager漏洞的调试分析了。


BeanValidation RCE 漏洞复现分析


CVE-2018-16621 分析


环境搭建

首先是漏洞环境的搭建,该漏洞的影响版本范围为:Nexus Repository Manager OSS/Pro 3.x - 3.13

选择Nexus Repository Manager OSS 3.13为测试,先下载对应版本源码:https://github.com/sonatype/nexus-public.git

然后拉取对应版本的docker镜像进行搭建:

docker pull sonatype/nexus3:3.13.0

运行容器,因为选择以远程调试的方式,所以在运行容器时要开启JDWP调试端口映射:

docker run -d -p 8081:8081 -p 8000:8000 --name nexus -e INSTALL4J_ADD_VM_PARAMS="-Xms2g -Xmx2g -XX:MaxDirectMemorySize=3g -Djava.util.prefs.userRoot=${NEXUS_DATA}/javaprefs -Dstorage.diskCache.diskFreeSpaceLimit=1024 -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000" sonatype/nexus3:3.13.0

参数介绍:

-p 选项指定端口映射,这里8000端口为映射远程调试端口

-e 选项指定容器环境变量,INSTALL4J_ADD_VM_PARAMS为动态调试参数

开启容器后,浏览器访问映射端口8081即可,默认密码为admin/admin123

然后导入项目源码到Idea,配置远程调试:

BeanValidation RCE 漏洞复现分析


复现分析

参考网上给出的poc,先登录账号admin,获取管理员session,然后发送如下数据包:

POST /service/extdirect HTTP/1.1
Host: 192.168.52.128:8081
Content-Length: 336
X-Requested-With: XMLHttpRequest
X-Nexus-UI: true
Content-Type: application/json
Accept: */*
Origin: http://192.168.52.128:8081
Referer: http://192.168.52.128:8081/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: Hm_lvt_866c9be12d4a814454792b1fd0fed295=1641052730; Hm_lvt_df6f78cfc7b28956736ab98287309c75=1641052807; NXSESSIONID=3c62bb00-aef5-4390-8140-e211332f8eac
Connection: close

{
  "action": "coreui_User",
  "method": "create",
  "data": [{
      "userId": "admin",
      "version": "2",
      "firstName": "admin",
      "lastName": "User",
      "email": "[email protected]",
      "status": "active",
      "roles": ["exp|${111*2}|"]
  }],
  "type": "rpc",
  "tid": 11
}

BeanValidation RCE 漏洞复现分析


可以从返回包中看到表达式成功执行。

那么一开始怎么入手呢?可以考虑到javax.servlet.http.HttpServlet#service方法先下断点跟

BeanValidation RCE 漏洞复现分析

BeanValidation RCE 漏洞复现分析

跟进service方法后先是判断了POST请求后,继续跟进到HttpServlet的子类DirectJNgineServlet的doPost方法

BeanValidation RCE 漏洞复现分析

前面都是一些编码处理,然后调用了processRequest方法进行处理请求,跟进

BeanValidation RCE 漏洞复现分析

接着根据Content-Type进入case JSON分支,调用了RequestRouter类的processJsonRequest方法

BeanValidation RCE 漏洞复现分析


BeanValidation RCE 漏洞复现分析

在JsonRequestProcessor#process方法中先是调用了getIndividualJsonRequests方法解析了请求包中的json数据,得到action为coreui_User,method为create,接着调用processIndividualRequestsInThisThread方法

BeanValidation RCE 漏洞复现分析


BeanValidation RCE 漏洞复现分析

通过获取到的action和method作为参数传入dispatchStandardMethod方法,开始进行调度

BeanValidation RCE 漏洞复现分析

后面也可以可以直接找到对应的action和method,在UserComponent#create方法断点跟进,原因是UserComponent类通过@DirectAction注解的方式注入了action,也就是我们请求的coreui_User action,也通过@DirectMethod注解注入了对应的methond

BeanValidation RCE 漏洞复现分析

同时也可以看到使用了@Valid注解对UserXO进行了验证,我们可以跟进UserXO类查看分别使用了哪些Validator,如下图所示,另外我们从poc中的漏洞位置可以判断是跟其中的roles属性有关,于是重点关注roles

BeanValidation RCE 漏洞复现分析

这里roles属性标注了自定义的@RolesExist注解,跟进查看

BeanValidation RCE 漏洞复现分析


可以发现最终的验证处理类是RolesExistValidator类,于是就可以直接在RolesExistValidator的isValid方法处进行断点,并直接


BeanValidation RCE 漏洞复现分析


那RolesExistValidator是在调用栈中何时进行获取的呢?

观察调用栈发现是在ConstraintTree#validateConstraints方法中,可以看到依次获取UserXO中使用到的各种Validator,其中就包含了RolesExistValidator

BeanValidation RCE 漏洞复现分析


回到RolesExistValidator#isValid方法中


BeanValidation RCE 漏洞复现分析


因为验证不通过,捕获异常后missing不为空,写入自定义的错误信息,也就是运行到这个漏洞的关键代码:

context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("Missing roles: " + missing).addConstraintViolation();

而我们构造的表达式注入payload也作为参数传入了context.buildConstraintViolationWithTemplate,并且调用了addConstraintViolation

BeanValidation RCE 漏洞复现分析


导致ConstraintViolation不为空,接着返回到调用栈中的ConstraintTree#validateSingleConstraint,后续return时调用了executionContext.createConstraintViolations,并且将刚刚创建的ConstraintViolation作为参数传入

BeanValidation RCE 漏洞复现分析


然后不断跟进几个interpolate方法,来到org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator#interpolateExpression方法

BeanValidation RCE 漏洞复现分析


其中在实例化InterpolationTerm时,如果传进来的参数以"$"开头,那么会将其中的属性type赋值“InterpolationTermType.EL”

BeanValidation RCE 漏洞复现分析


而我们的payload就是以“$”开头的

BeanValidation RCE 漏洞复现分析


也就导致了后面调用了interpolateExpressionLanguageTerm(context)

BeanValidation RCE 漏洞复现分析


最后在InterpolationTerm#interpolateExpressionLanguageTerm方法中执行了表达式解析

BeanValidation RCE 漏洞复现分析



其它漏洞点挖掘


调试完整漏洞后就可以发现,实际上是在开发者自定义验证处理时,把验证后的错误信息传入以下这行代码造成的

context.buildConstraintViolationWithTemplate("Missing roles: " + missing).addConstraintViolation();

于是我们可以再寻找其他类似的Validator,比如PrivilegesExistValidator

BeanValidation RCE 漏洞复现分析

再全局查找RolesExistValidator是哪个注解的处理类,以及哪个地方使用了这个注解,查找完整的调用方式

BeanValidation RCE 漏洞复现分析


BeanValidation RCE 漏洞复现分析


可以发现在RoleXO中就使用了@PrivilegesExist注解,于是构造数据包:

POST /service/extdirect HTTP/1.1
Host: 192.168.52.128:8081
Content-Length: 205
X-Requested-With: XMLHttpRequest
X-Nexus-UI: true
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://192.168.52.128:8081
Referer: http://192.168.52.128:8081/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: Hm_lvt_866c9be12d4a814454792b1fd0fed295=1641052730; Hm_lvt_df6f78cfc7b28956736ab98287309c75=1641052807; NXSESSIONID=f0facde6-ab42-45a5-a3de-db7ad822be61
Connection: close

{"action":"coreui_Role","method":"create","data":[{"version":"","source":"default","id":"123","name":"123","description":"123","privileges":["nx-all|${111*3}"],"roles":["nx-admin"]}],"type":"rpc","tid":36}

BeanValidation RCE 漏洞复现分析

这个版本还有其他的Validator存在类似的问题,这里就不一一列举了。

BeanValidation RCE 漏洞复现分析


CVE-2020-10204 分析


这个漏洞影响版本Nexus Repository Manager OSS/Pro 3.x -3.21.1,实际上是对上面的漏洞修复后的绕过

BeanValidation RCE 漏洞复现分析

通过diff可以看到修复方式就是加了getEscapeHelper().stripJavaEL对el表达式做了清除,将${替换为了{,但是可以用$\A{1*333}这样的方式绕过,关键的漏洞原理基本一致。

BeanValidation RCE 漏洞复现分析

BeanValidation RCE 漏洞复现分析


总结

        通过调试以及参考网上文章分析,对Nexus3的漏洞有了一个大体了解,问题主要是出现在利用Bean Validation进行验证数据时,在自定义的Validator处理逻辑中将错误信息被当作EL表达式进行执行。在进行漏洞挖掘时可以通过全局搜索漏洞根源代码中的关键字buildConstraintViolationWithTemplate 定位审计点,判断是否将错误信息传入以及是否有过滤处理,再向上溯源查找bean传入点,构造请求数据包进行调试。

BeanValidation RCE 漏洞复现分析


Reference


https://xz.aliyun.com/t/10693#toc-0

https://support.sonatype.com/hc/en-us/articles/360044356194

https://blog.knownsec.com/2020/07/nexus-repository-manager-3-%E5%87%A0%E6%AC%A1%E8%A1%A8%E8%BE%BE%E5%BC%8F%E8%A7%A3%E6%9E%90%E6%BC%8F%E6%B4%9E/


BeanValidation RCE 漏洞复现分析


原文始发于微信公众号(山石网科安全技术研究院):BeanValidation RCE 漏洞复现分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年2月15日19:54:09
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   BeanValidation RCE 漏洞复现分析https://cn-sec.com/archives/779240.html

发表评论

匿名网友 填写信息