Nexus Repository Manager 漏洞分析

0nise  1476天前

author:r4v3zn@白帽汇安全研究院

前言

3 月 31 日 Nexus Repository Manager 官方发布了 CVE-2020-10199 CVE-2020-10204 的漏洞通告信息,两个漏洞均是由 Github Secutiry Lab 的是 @pwntester 发现的。

Nexus Repository 是一个开源的仓库管理系统,在安装、配置、使用简单的基础上提供了更加丰富的功能。

漏洞概述

CVE-2020-10199CVE-2020-10204 主要是由于可执行恶意 EL表达式 导致的。

CVE-2020-10199

该漏洞的最终触发是通过给 HelperBeanmessage 进行 EL表达式 注入。

影响范围

Nexus Repository Manager 3.x OSS / Pro <= 3.21.1

CVE-2020-10204

漏洞触发的主要原因是在org.sonatype.nexus.security.privilege.PrivilegesExistValidatororg.sonatype.nexus.security.role.RolesExistValidator 类中,会对不存在的 privilegerole 抛出错误,而在错误信息抛出的时候,会存在一个 EL表达式 的渲染,会提取其中的el表达式并执行,从而造成 EL表达式 注入。

影响范围

Nexus Repository Manager 3.x OSS / Pro <= 3.21.1

调试

下载 Docker 镜像:

docker pull sonatype/nexus3:3.21.1

创建 nexus 数据存储目录:

mkdir /your-dir/nexus-data

运行 Docker 镜像,并且开启调试端口,其中 8081 为 web 访问端口,5050 端口为远程调试端口:

docker run -d --rm -p 8081:8081 -p 5050:5050 --name nexus -v /your-dir/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.21.1

下载 Nexus 源码,并且切换至 3.21.0-05 分支:

git clone https://github.com/sonatype/nexus-public.git
git checkout -b release-3.21.0-05 origin/release-3.21.0-05

IDEA 导入项目并且配置远程调试信息:

1.png

分析

CVE-2020-10199

该漏洞主要是因为 HelperBeanmessage 并做 EL表达式过滤,导致出现 RCE 漏洞。根据作者作者描述,作者发现如果可控的数据进入到 createViolation 函数将会调用 buildConstraintViolationWithTemplate 执行EL表达式,在 org.sonatype.nexus.repository.rest.api.AbstractGroupRepositoriesApiResource#validateGroupMembers:97createViolation 会调用执行。

private void validateGroupMembers(T request) {
  String groupFormat = request.getFormat();
  Set<ConstraintViolation<?>> violations = Sets.newHashSet();
  Collection<String> memberNames = request.getGroup().getMemberNames();
  for (String repositoryName : memberNames) {
    Repository repository = repositoryManager.get(repositoryName);
    if (nonNull(repository)) {
      String memberFormat = repository.getFormat().getValue();
      if (!memberFormat.equals(groupFormat)) {
        violations.add(constraintViolationFactory.createViolation("memberNames",
            "Member repository format does not match group repository format: " + repositoryName));
      }
    }
    else {
      violations.add(constraintViolationFactory.createViolation("memberNames",
          "Member repository does not exist: " + repositoryName));
    }
  }
  maybePropagate(violations, log);
}

其中 GolangGroupRepositoriesApiResource 继承 AbstractGroupRepositoriesApiResource ,在执行 createRepositoryupdateRepository 会利用到 validateGroupMembers 从而触发漏洞:

其中 @path 为请求路径,通过变量拼接可以得出完整的路径为 beta/repositories/go/group ,也可以通过 Java Enterprise 可以看到请求路径为 beta/repositories/go/group

通过查看 swagger 可以看到请求的 base URL 为 /service/rest/ ,最后得出请求的完整链接为 /service/rest/beta/repositories/go/group

最后发起请求执行命令:

memberNames参数为什么需要把 String 变成 Array

可以看到请求的参数为 GolangGroupRepositoryApiRequest 类,该类中请求的参数 group 类型为 GroupAttributes

可以看到在 GroupAttributes 构造方法中需要一个参数名称为 memberNames 属性,该属性的类型为Collection<String>

通过查看 Collection 的继承链可以看到在有 ListSetQueue 等继承,也就是说请求的参数为其中继承某一个类型就可以实现。

回到正题,为什么 String 类型的不行,查看 String 类,源代码可以看到 String 并未继承 Collection ,所以无法正常使用。

CVE-2020-10204

CVE-2020-10204 为 CVE-2018-16621 的绕过,官方在修复的漏洞采用的方案是新增 org.sonatype.nexus.common.template.EscapeHelper.stripJavaEl:81 ,对用户输入roles参数进行过滤,正则匹配的结果是将‘${’替换为‘{ ’,从而防止EL表达式注入,代码片段如下:

  /**
   * Strip java el start token from a string
   * @since 3.14
   */
  public String stripJavaEl(final String value) {
    if (value != null) {
      return value.replaceAll("\\$+\\{", "{");
    }
    return null;
  }

漏洞触发主要是由于 org.sonatype.nexus.security.privilege.PrivilegesExistValidatororg.sonatype.nexus.security.role.RolesExistValidator 类中,会将没有找到的 privilege 或 role 放入错误模板中,而在错误模板在渲染的时候会提取其中的EL表达式并执行。

最后发起请求执行命令:

EL表达式执行流程

进入 EL表达式 流程之后会通过org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree#validateConstraints 进行校验表达式,然后通过 addConstraintFailure 进行添加 validationContext

跟进 addConstraintFailure 之后可以看到 messageTemplate 为我们提交的 EL表达式 信息,然后通过 this.interpolate 进行执行该表达式。

跟进 this.interpolate 可以看到通过 this.validatorScopedContext.getMessageInterpolator().interpolate(messageTemplate, context); 进行执行,其中 messageTemplate 参数为我们提交的 EL表达式

跟进 org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator#interpolate 之后,将提交的 EL表达式 作为参数交由 this.interpolateMessage(message, context, this.defaultLocale); 处理。

继续跟进 org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator#interpolateMessage ,然后通过 this.interpolateex pression 进行针对表达式进行处理,然后将处理结果赋值给 resolvedMessage,并且作为参数再次处理表达式。

此时执行的代码为 this.interpolateex pression(new TokenIterator(this.getParameterTokens(resolvedMessage, this.tokenizedELMessages, InterpolationTermType.EL)), context, locale); 跟进代码可以看到 isEL=True,然后通过 this.interpolate 处理。

跟进 org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator#interpolate:83 可以看到已经将我们提交的 EL表达式 实例化为 EL 类型对象。

最后通过 org.hibernate.validator.internal.engine.messageinterpolation.ElTermResolver#interpolate 方法中的 resolvedex pression = (String)valueex pression.getValue(elContext); 进行执行表达式,然后将执行结果转换为 String 类型结果内容进行响应。

以下为整个的调用链条:

修复

升级至最新版本或 Nexus Repository Manager 3.x OSS / Pro > 3.21.1

参考

最新评论

昵称
邮箱
提交评论