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)。
然后直接解压,并拖到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调试的配置:
完事之后进入刚才的bin目录,通过teamcity-server start启动teamcity server;如果是第一次启动的话需要配置数据库位置、admin的账号密码等。
弄完这些之后去idea里面打个断点开启调试,并通过yakit等发包测试是否配置成功进入调试:
二、漏洞分析
你需要把一些文件夹里面的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函数这里打下断点,开启调试:
F7步入即可看到,由于我们请求的/hax不存在,因此直接分发至PageNotFoundController来处理:
如果我们请求的是/app/rest/server,那么就就会分发至UnauthorizedErrorController:
F8接着往下走,经过函数updateViewIfRequestHasJspParameter的时候应该警觉起来,从函数的名字来看,意思就是看请求是否有JSP字段也就是有没有?jsp=xxx,如果有,那就更新view:
果断F7进去看看,程序从request的jsp字段拿到了一个viewName也就是/app/rest/server;.jsp:
继续F8,我们希望看到的是获取到了新的View之后的渲染过程,因此我们步入processDispatchResult这个函数:
继续F8即可看到我们期待的render函数:
步入之后F8继续走,我们希望了解的是/app/rest/server;.jsp是如何被处理的,因此我们步入resolveViewName函数:
出现两个紫色快,左键点击第一个即可步入resolveViewName:
里面的逻辑比较简单,就不细说了,返回结果是一个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步入:
继续走,F7步入renderMergedOutputModel函数:
步入getRequestDispatcher函数,字面意思就是获取RequestDispatcher的过程:
左键单击第二个步入:
接着一步步按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:
尝试替换:
发现没用,还是403:
尝试删除X-Tc-Csrf-Token,还是403:
继续删除Cookie,直接500:
这时候我还没看代码调试,从之前的报错CSRF Header X-TC-CSRF-Token does not match CSRF session value猜想是session对应一个csrf token。尝试从浏览器访问目标,然后获取自动生成的TCSESSIONID:
然后拿到403报错后的csrf_token:
进行替换,发现冰蝎3连上去了:
到这里我以为再添加个自定义请求头应该就可以访问了,结果还是不行:
原来这冰蝎3没给你加cookie,我尝试手动添加,发现还是不行:
原来这里会有两个,一个是识别到cookie字段后冰蝎加的,另一个是我们自定义的。
由于之前我一直没关注冰蝎4,所以这个地方我想了挺久,准备自己动手改冰蝎源码了,直到我看到了冰蝎4的changelog:
结束!
我写的脚本地址如下:
https://github.com/W01fh4cker/CVE-2024-27198-RCE
到此,就可以实现任意版本的TeamCity漏洞存在即可打了:
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