From cba2d545b6300858016c2a7b6b4a160c1838adb0 Mon Sep 17 00:00:00 2001 From: tk Date: Mon, 9 Jun 2025 21:37:34 +0900 Subject: [PATCH 1/3] =?UTF-8?q?client=5Fsecret=20=EA=B2=80=EC=A6=9D(query,?= =?UTF-8?q?body,header)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addon/cleintsecret_check.py | 64 +++++++++++++++++++++++++++++++++++++ addon/init.py | 14 +++++++- 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 addon/cleintsecret_check.py diff --git a/addon/cleintsecret_check.py b/addon/cleintsecret_check.py new file mode 100644 index 0000000..9f431ab --- /dev/null +++ b/addon/cleintsecret_check.py @@ -0,0 +1,64 @@ +# client_secret_check.py +from mitmproxy import http, ctx +from urllib.parse import urlparse, parse_qs +from typing import Optional, List +import lib.target as target +from lib.report import save_report + + +class ClientSecretChecker: + def is_oauth_uri(self, uri: str) -> bool: + qs = parse_qs(urlparse(uri).query) + keys = qs.keys() + return "client_id" in keys and ("client_secret" in keys or "client_secret" in uri) + + def has_client_secret_in_uri(self, uri: str) -> bool: + qs = parse_qs(urlparse(uri).query) + return "client_secret" in qs + + def is_post_with_client_secret(self, flow: http.HTTPFlow) -> bool: + if flow.request.method != "POST": + return False + content_type = flow.request.headers.get("Content-Type", "") + if "application/x-www-form-urlencoded" in content_type: + body = parse_qs(flow.request.get_text()) + return "client_secret" in body + return False + + def is_exposed_in_referer(self, flow: http.HTTPFlow) -> bool: + referer = flow.request.headers.get("Referer", "") + return "client_secret" in referer + + def check_client_secret_leak(self, flow: http.HTTPFlow) -> List[str]: + messages = [] + + if self.has_client_secret_in_uri(flow.request.url): + messages.append("client_secret found in URL query string") + + if self.is_post_with_client_secret(flow): + messages.append("client_secret found in POST body") + + if self.is_exposed_in_referer(flow): + messages.append("client_secret exposed in Referer header") + + return messages + + def response(self, flow: http.HTTPFlow) -> None: + try: + if not self.is_oauth_uri(flow.request.url): + return + + issues = self.check_client_secret_leak(flow) + if issues: + desc = " | ".join(issues) + report_data = [{ + 'target': target.load(), + 'status': "HIGH", + 'title': "OAuth Client Secret Exposure", + 'description': desc, + 'uri': flow.request.url, + }] + save_report(report_data) + print(f"[INFO] Client Secret Check: {desc}") + except Exception as e: + print(f"[ERROR] Client Secret Check failed: {e}") diff --git a/addon/init.py b/addon/init.py index 1ef743d..29e7027 100644 --- a/addon/init.py +++ b/addon/init.py @@ -4,6 +4,7 @@ from pkce_check import PKCEDowngradeChecker from ScopeDetection import ScopeDetection from csrf_check import CsrfChecker from nonce_check import NonceChecker +from cleintsecret_check import ClientSecretChecker class PKCEAddon: def __init__(self): @@ -60,5 +61,16 @@ class NonceAddon: except Exception as e: print(f"[ERROR] NonceAddon failed: {e}") pass + +class ClientSecretAddon: + def __init__(self): + self.checker = ClientSecretChecker() -addons = [PKCEAddon(), ScopeAddon(), CsrfAddon(), NonceAddon()] + async def request(self, flow: http.HTTPFlow): + try: + self.checker.response(flow) + except Exception as e: + print(f"[ERROR] ClientSecretAddon failed: {e}") + pass + +addons = [PKCEAddon(), ScopeAddon(), CsrfAddon(), NonceAddon(), ClientSecretAddon()] From 4059cc7adb474c057db26e5f4a2510a96be4fc73 Mon Sep 17 00:00:00 2001 From: tk Date: Mon, 9 Jun 2025 23:12:28 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addon/cleintsecret_check.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/addon/cleintsecret_check.py b/addon/cleintsecret_check.py index 9f431ab..481bfe6 100644 --- a/addon/cleintsecret_check.py +++ b/addon/cleintsecret_check.py @@ -1,9 +1,5 @@ -# client_secret_check.py -from mitmproxy import http, ctx +from mitmproxy import http from urllib.parse import urlparse, parse_qs -from typing import Optional, List -import lib.target as target -from lib.report import save_report class ClientSecretChecker: @@ -29,7 +25,7 @@ class ClientSecretChecker: referer = flow.request.headers.get("Referer", "") return "client_secret" in referer - def check_client_secret_leak(self, flow: http.HTTPFlow) -> List[str]: + def check_client_secret_leak(self, flow: http.HTTPFlow) -> list[str]: messages = [] if self.has_client_secret_in_uri(flow.request.url): @@ -43,22 +39,14 @@ class ClientSecretChecker: return messages - def response(self, flow: http.HTTPFlow) -> None: + async def request(self, flow: http.HTTPFlow) -> None: try: if not self.is_oauth_uri(flow.request.url): return issues = self.check_client_secret_leak(flow) if issues: - desc = " | ".join(issues) - report_data = [{ - 'target': target.load(), - 'status': "HIGH", - 'title': "OAuth Client Secret Exposure", - 'description': desc, - 'uri': flow.request.url, - }] - save_report(report_data) - print(f"[INFO] Client Secret Check: {desc}") + print(f"[HIGH] OAuth Client Secret Exposure: {' | '.join(issues)}") + print(f"[URL] {flow.request.url}") except Exception as e: print(f"[ERROR] Client Secret Check failed: {e}") From 568d3f0ce5d1445e442c569267c0fe614cd8461b Mon Sep 17 00:00:00 2001 From: tk Date: Mon, 9 Jun 2025 23:35:52 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20req,res=20=EA=B5=AC?= =?UTF-8?q?=EB=B6=84=20=EC=BD=94=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addon/cleintsecret_check.py | 36 ++++++++++++++++++++++++++++++------ addon/init.py | 9 ++++++++- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/addon/cleintsecret_check.py b/addon/cleintsecret_check.py index 481bfe6..f48ed93 100644 --- a/addon/cleintsecret_check.py +++ b/addon/cleintsecret_check.py @@ -1,5 +1,9 @@ +# clientsecret_check.py from mitmproxy import http from urllib.parse import urlparse, parse_qs +from typing import List +import lib.target as target +from lib.report import save_report class ClientSecretChecker: @@ -25,7 +29,7 @@ class ClientSecretChecker: referer = flow.request.headers.get("Referer", "") return "client_secret" in referer - def check_client_secret_leak(self, flow: http.HTTPFlow) -> list[str]: + def check_client_secret_leak(self, flow: http.HTTPFlow) -> List[str]: messages = [] if self.has_client_secret_in_uri(flow.request.url): @@ -39,14 +43,34 @@ class ClientSecretChecker: return messages - async def request(self, flow: http.HTTPFlow) -> None: + def _report(self, flow: http.HTTPFlow, issues: List[str], direction: str): + desc = " | ".join(issues) + report_data = [{ + 'target': target.load(), + 'status': "HIGH", + 'title': f"OAuth Client Secret Exposure ({direction})", + 'description': desc, + 'uri': flow.request.url, + }] + save_report(report_data) + print(f"[INFO] Client Secret Leak Detected ({direction}): {desc}") + + def request(self, flow: http.HTTPFlow) -> None: try: if not self.is_oauth_uri(flow.request.url): return - issues = self.check_client_secret_leak(flow) if issues: - print(f"[HIGH] OAuth Client Secret Exposure: {' | '.join(issues)}") - print(f"[URL] {flow.request.url}") + self._report(flow, issues, "request") except Exception as e: - print(f"[ERROR] Client Secret Check failed: {e}") + print(f"[ERROR] Client Secret Check (request) failed: {e}") + + def response(self, flow: http.HTTPFlow) -> None: + try: + if not self.is_oauth_uri(flow.request.url): + return + issues = self.check_client_secret_leak(flow) + if issues: + self._report(flow, issues, "response") + except Exception as e: + print(f"[ERROR] Client Secret Check (response) failed: {e}") diff --git a/addon/init.py b/addon/init.py index 29e7027..d84c551 100644 --- a/addon/init.py +++ b/addon/init.py @@ -67,10 +67,17 @@ class ClientSecretAddon: self.checker = ClientSecretChecker() async def request(self, flow: http.HTTPFlow): + try: + self.checker.request(flow) + except Exception as e: + print(f"[ERROR] ClientSecretAddon request failed: {e}") + pass + + async def response(self, flow: http.HTTPFlow): try: self.checker.response(flow) except Exception as e: - print(f"[ERROR] ClientSecretAddon failed: {e}") + print(f"[ERROR] ClientSecretAddon response failed: {e}") pass addons = [PKCEAddon(), ScopeAddon(), CsrfAddon(), NonceAddon(), ClientSecretAddon()]