import jwt from urllib.parse import urlparse, parse_qs from typing import Union import httpx import lib.target as target from lib.report import save_report class NonceChecker: def is_oidc_flow(self, flow) -> bool: res = flow.response url = res.url parsed = urlparse(url) fragment_params = parse_qs(parsed.fragment) location = res.headers.get("location", "") content_type = res.headers.get("content-type", "") if "application/json" in content_type: if "id_token" in res.text: return True if self.extract_id_token(self, flow): return True if res.status_code in [302, 303]: if isinstance(location, list): location = location[0] if "id_token=" in location: return True if "id_token" in fragment_params: return True return False def extract_id_token(self, flow) -> Union[str, None]: """ 응답에서 id_token을 추출하는 함수. """ res = flow.response # 1. JSON 응답에 id_token 있음 if "application/json" in res.headers.get("content-type", ""): try: data = res.json() return data.get("id_token") except Exception: pass # 2. Location 헤더에서 id_token 파싱 (예: #id_token=...&access_token=...) location = res.headers.get("location", "") if location: if "#" in location: fragment = location.split("#")[1] params = parse_qs(fragment) return params.get("id_token", [None])[0] elif "?" in location: query = location.split("?")[1] params = parse_qs(query) return params.get("id_token", [None])[0] return None def decode_id_token(self, flow) -> dict: res = flow.response id_token = self.extract_id_token(flow) if not id_token: return {} try: return jwt.decode(id_token, options={"verify_signature": False}) except Exception as e: return {} def check_nonce_in_id_token(self, flow) -> bool: if not flow.response or not self.is_oidc_flow(flow): # OIDC 플로우가 아니거나 응답이 없으면 nonce 체크를 건너뜀 return True res = flow.response url = res.url parsed = urlparse(url) fragment_params = parse_qs(parsed.fragment) if "id_token" in fragment_params: # id_token이 fragment에 있는 경우 id_token = fragment_params["id_token"][0] return True id_token = self.extract_id_token(flow) # TODO id_token을 파싱하는 부분이 누락되어있습니다. def check_nonce_in_id_token(self, flow, id_token: str) -> bool: decoded = self.decode_id_token(id_token) nonce = decoded.get("nonce") if not nonce: report_data = [{ 'target': target.load(), 'status': "MEDIUM", 'title': "nonce is missing in id_token", 'description': "Nonce is present in the request but missing in the id_token.", 'uri': f"Original: {url}\nDecoded ID Token: {decoded}", }] save_report(report_data) return False else: return True