oauth-backend/addon/nonce_check.py
2025-06-10 02:13:29 +09:00

107 lines
3.3 KiB
Python

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)
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