JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用

原创 W01fh4cker 追梦信安 2024-03-12 00:01

一、环境搭建

单纯的验证的话,使用docker就可以了:

sudo docker pull jetbrains/teamcity-server:2023.11.3
sudo docker run -it -d --name teamcity -u root -p 8111:8111 jetbrains/teamcity-server:2023.11.3
# sudo ufw disable

这里我们使用windows+IDEA远程调试的方式来调试代码。

先去官方下载exe文件:

https://www.jetbrains.com/teamcity/download/other.html

我选择的版本是2023.11.3,如果是2023.05.x的版本的话,一些文件位置会不对,一些文件会没有(例如web-openapi.jar)。

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -1

然后直接解压,并拖到IDEA里面打开:

在conf文件夹下新建server.xml:

<?xml version='1.0' encoding='utf-8'?>
<Server port="8105" shutdown="SHUTDOWN">
    <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
    <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
    <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
    <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
    <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
    <GlobalNamingResources>
        <Resource name="UserDatabase" auth="Container"                  type="org.apache.catalina.UserDatabase"                  description="User database that can be updated and saved"                  factory="org.apache.catalina.users.MemoryUserDatabaseFactory"                  pathname="conf/tomcat-users.xml" />
    </GlobalNamingResources>
    <Service name="Catalina">
        <Connector port="8111" protocol="org.apache.coyote.http11.Http11NioProtocol"                   connectionTimeout="60000"                   redirectPort="8543"                   useBodyEncodingForURI="true"                   tcpNoDelay="1"                   maxHttpHeaderSize="16000"        />
        <Engine name="Catalina" defaultHost="localhost">
            <Realm className="org.apache.catalina.realm.LockOutRealm">
                <Realm className="org.apache.catalina.realm.UserDatabaseRealm"                       resourceName="UserDatabase"/>
            </Realm>
            <Host name="localhost"  appBase="webapps"                  unpackWARs="true" autoDeploy="true">

                <Valve className="org.apache.catalina.valves.ErrorReportValve"                       showReport="false"                       showServerInfo="false" />
            </Host>
        </Engine>
    </Service>
</Server>

打开TeamCity-2023.11.3\bin\catalina.bat,在第一行插入如下代码:

SET CATALINA_OPTS=-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8188

并进行远程JVM调试的配置:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -3

完事之后进入刚才的bin目录,通过teamcity-server start启动teamcity server;如果是第一次启动的话需要配置数据库位置、admin的账号密码等。

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -4

弄完这些之后去idea里面打个断点开启调试,并通过yakit等发包测试是否配置成功进入调试:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -5

二、漏洞分析

你需要把一些文件夹里面的lib文件夹右键Add as Libriary…来添加到依赖之中,以避免大量的报错影响阅读。建议可以先F8配合F7一步步调试完整个代码,对途径的函数名称有所熟悉。

TeamCity中负责请求处理分发的功能点位于TeamCity-2023.11.3\webapps\ROOT\WEB-INF\lib\web-openapi.jar!\jetbrains\buildServer\controllers\BaseController.class,看到handleRequest函数的最后调用了handleRequestInternal,因此我们在handleRequestInternal函数这里打下断点,开启调试:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -6

F7步入即可看到,由于我们请求的/hax不存在,因此直接分发至PageNotFoundController来处理:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -7

如果我们请求的是/app/rest/server,那么就就会分发至UnauthorizedErrorController:

F8接着往下走,经过函数updateViewIfRequestHasJspParameter的时候应该警觉起来,从函数的名字来看,意思就是看请求是否有JSP字段也就是有没有?jsp=xxx,如果有,那就更新view:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -9

果断F7进去看看,程序从request的jsp字段拿到了一个viewName也就是/app/rest/server;.jsp:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -10

继续F8,我们希望看到的是获取到了新的View之后的渲染过程,因此我们步入processDispatchResult这个函数:

继续F8即可看到我们期待的render函数:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -12

步入之后F8继续走,我们希望了解的是/app/rest/server;.jsp是如何被处理的,因此我们步入resolveViewName函数:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -13

出现两个紫色快,左键点击第一个即可步入resolveViewName:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -14

里面的逻辑比较简单,就不细说了,返回结果是一个JstlView的view:

官网里面讲的很清楚,内部资源视图解析器InternalResourceViewResolver会将视图名称viewName解析为JSP文件的路径,并将其包装在一个RequestDispatcher对象中,然后将请求发送到该RequestDispatcher,从而执行JSP的渲染过程;比如我们之前所看到的404.jsp解析成404.html:

https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/viewresolver.html

继续F8往下走,我们还是点击render步入:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -17

继续走,F7步入renderMergedOutputModel函数:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -18

步入getRequestDispatcher函数,字面意思就是获取RequestDispatcher的过程:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -19

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -20

左键单击第二个步入:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -21

接着一步步按F7,来到org.apache.catalina.core.ApplicationContext#getRequestDispatcher,可以看到就是在这一步传入的路径进行了处理,把本来不存在的/app/rest/server;.jsp变成了存在的/app/rest/server,这样就可以更改正在处理url的DispatcherServlet,从而实现可以访问任意端点:

但是分析到这里,还是没有说清楚一个关键的事情,那就是为什么我们访问一个不存在的uri,例如这里的/hax,他就会不进行鉴权呢?要搞明白这个事情就得去调试jetbrains.buildServer.controllers.interceptors.RequestInterceptors#preHandle这个方法,我调试的时候访问了不存在的uri,这里显示var4.size() == 1,然后进入多个interceptor的prehandle方法。

调试的时候发现,由于反编译失败,很多代码都看不了,但是可以确定的是,uri不存在导致它们的判断的结果都为true,导致最终applyPreHandle的结果为true。这个地方后面需要再研究,如果有想法的师傅也可以直接留言交流,谢谢指教。

三、漏洞利用

主要有两个RCE方法,一个是请求/app/rest/debug/processes,另一个是后台构造上传恶意插件传webshell。

但是由于之前的CVE-2023-42793,因此官方直接删除了这个端点,也就是2023.11.x之后的版本都只能考虑第二个方法,参考:

https://youtrack.jetbrains.com/issue/TW-85379

这部分通过抓包就可以写出来。但是当我传完behinder3.0的shell后,发现无法连接,抓包发现提示403:

尝试替换:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -24

发现没用,还是403:

尝试删除X-Tc-Csrf-Token,还是403:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -26

继续删除Cookie,直接500:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -27

这时候我还没看代码调试,从之前的报错CSRF Header X-TC-CSRF-Token does not match CSRF session value猜想是session对应一个csrf token。尝试从浏览器访问目标,然后获取自动生成的TCSESSIONID:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -28

然后拿到403报错后的csrf_token:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -29

进行替换,发现冰蝎3连上去了:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -30

到这里我以为再添加个自定义请求头应该就可以访问了,结果还是不行:

原来这冰蝎3没给你加cookie,我尝试手动添加,发现还是不行:

原来这里会有两个,一个是识别到cookie字段后冰蝎加的,另一个是我们自定义的。

由于之前我一直没关注冰蝎4,所以这个地方我想了挺久,准备自己动手改冰蝎源码了,直到我看到了冰蝎4的changelog:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -33

结束!

我写的脚本地址如下:

https://github.com/W01fh4cker/CVE-2024-27198-RCE

到此,就可以实现任意版本的TeamCity漏洞存在即可打了:

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -34

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -35

JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用 -36

rapid7的博客中说,插件即使删除也会因为只有先禁用才能删除这个特性导致插件会在disabled-plugins.xml中留下永久的一条,比如C:\ProgramData\JetBrains\TeamCity\config\disabled-plugins.xml:

<?xml version="1.0" encoding="UTF-8"?>
<disabled-plugins>
  <disabled-plugin name="WYyVNA6r" />
</disabled-plugins>

我测试之后发现不太对,可以直接删除,但是需要对teamcity进行重启,而先禁用再删除则不需要重启。

关于teamcity的后渗透的东西,可以参考:

https://github.com/kacperszurek/pentest_teamcity