一、环境搭建
单纯的验证的话,使用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.3bincatalina.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.3webappsROOTWEB-INFlibweb-openapi.jar!jetbrainsbuildServercontrollersBaseController.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:ProgramDataJetBrainsTeamCityconfigdisabled-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
原文始发于微信公众号(追梦信安):JetBrains TeamCity权限绕过(CVE-2024-27198)分析与利用
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论