Nexus Repository Manager 3 CVE-2019-7238 IDEA远程调试

漏洞公告: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://cloud.tencent.com/developer/article/1390628

两周之前出现的一个漏洞(结果我拖了两周才复现),有很多师傅已经写了复现文章,我研读了师傅们的文章,然后自己动手操作了两三天,虽然不是第一次接触 Java 的漏洞,不过这是我第一次使用IDEA来进行远程调试,所以写一篇文章记录一下。


环境搭建

因为项目依赖繁多的关系,所以我们使用docker来进行环境搭建。

docker:https://hub.docker.com/r/sonatype/nexus3/

源码:https://github.com/sonatype/nexus-public

注意一下要选择3.14版本的docker的源码,3.15版本已经对这个漏洞进行了修复。

因为我们要使用IDEA远程调试,自然就不能使用它docker自带的服务启动命令,所以我们要对它的启动脚本进行修改。这里我们可以使用docker inspect查看镜像的详细信息,可以看到服务启动用的是start-nexus-repository-manager.sh这个sh文件,而这个sh文件则会以run为参数运行/opt/sonatype/nexus/bin/nexus这个脚本,所以我们稍加修改,给它的启动命令加上这么一些参数,就可以开放8888端口供我们调试使用:

1
-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8888

修改后:

1
2
3
4
    ;;
run)

$INSTALL4J_JAVA_PREFIX exec "$app_java_home/bin/java" -Xdebug -server -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8888 -Dinstall4j.jvmDir="$app_java_home" -Dexe4j.moduleName="$prg_dir/$progname" "-XX:+UnlockDiagnosticVMOptions" "-Dinstall4j.launcherId=245" "-Dinstall4j.swt=false" "$vmov_1" "$vmov_2" "$vmov_3" "$vmov_4" "$vmov_5" $INSTALL4J_ADD_VM_PARAMS -classpath "$local_classpath" com.install4j.runtime.launcher.UnixLauncher run 9d17dc87 "" "" org.sonatype.nexus.karaf.NexusMain

为了查看日志方便和防止出什么奇怪的问题,所以我用Dockerfile制作了一个新的镜像:

1
2
3
4
5
6
7
FROM sonatype/nexus3:3.14.0
MAINTAINER twings <1980750808@qq.com>

ADD nexus /opt/sonatype/nexus/bin/nexus

EXPOSE 8081 8888
CMD ["/bin/bash"]

8081是Web服务的端口,8888是远程调试端口,然后使用:

1
docker run -id --name nexus -p 32770:8081 -p 8888:8888 nexus

运行镜像。然后我们exec进入容器里面,运行start-nexus-repository-manager.sh开启Web服务,这样一来测试环境就搭建完成了,

IDEA配置

用IDEA打开源码文件夹,等待maven将代码下载完,然后点击右上方Edit Configuration配置远程调试:

然后我们点击右上角的debug按钮即可开始远程调试。

复现

根据漏洞利用的披露截图可以得知漏洞点在coreui_Component的previewAssets方法,即:

我們也可以3.15版本的更新中看出这里多了访问权限控制的代码,也符合披露中对漏洞的描述:

从previewAssets函数的代码中我们可以看到这个函数会接受我们传入的表达式并执行,表达式有两种,分别为csel和jexl,而代码对csel表达式做了一些限制,所以我们只能通过执行jexl表达式来进行RCE。

跟踪函数,可以看到我们的表达式作为参数被传入browseService.previewAssets进行下一步执行,我们继续追踪,可以看到表达式会生成SQL语句后交给storageTx:

我们一步步跟踪findAssets和countAssets两个函数,可以看到最后形成的SQL形如:

1
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, :jexlExpression, :repositorySelector, :repoToContainedGroupMap) == true)

因为两个条件使用and相连,也就是说where条件前面的不满足,就不会执行他的自定义函数contentExpression,那我们的表达式也就不会执行了,这就是为什么新搭建的环境需要在现有的Repository添加asset的原因了。

我们访问网站,用admin/admin123的账号登陆上去,然后给maven-releases仓库添加一个asset,任意一个Jar文件都可以:

添加之后,发包:

命令成功执行。

最后

iswin大师傅提到的能够获取回显的方式,我尝试了这么几种方式:

  1. jexl表达式

  2. 反射thread-currentthread()-threadlocals

  3. 通过别的类的函数获取threadlocals变量

然而调试了两三天还是无法得出,只能知道threadlocals变量中有一个webcontext,而webcontext中有response对象,希望会的师傅能带带我Orz


参考文章:

https://www.anquanke.com/post/id/171116

https://xz.aliyun.com/t/4136