[Update] save vuln report logic

This commit is contained in:
tv0924@icloud.com 2025-06-26 12:20:41 +09:00
commit 3a1422a2f2
9 changed files with 121 additions and 190 deletions

View file

@ -4,23 +4,14 @@ from urllib.parse import urlparse, parse_qs, unquote
import httpx
from typing import Optional, Union, List
import lib.cur_target_url as cur_target_url
from lib.report_vuln import save_report
from lib.report_vuln import report_vuln
from lib.utils.is_oauth_uri import is_oauth_uri
class CsrfChecker:
nonce_params = {
"state", "nonce", "as", "frame_id", "csrf_token", "csrf"
}
def is_oauth_uri(self, uri: str) -> bool:
qs = parse_qs(urlparse(uri).query)
qs_keys = [*qs]
if "client_id" in qs_keys and any(p in qs_keys for p in (
"redirect_uri", "response_type", "grant_type", "scope", "state", "nonce")):
return True
return False
def get_header(self, headers: http.Headers, name: str) -> Optional[str]:
# mitmproxy Headers는 case-insensitive
raw = headers.get(name)
@ -40,7 +31,7 @@ class CsrfChecker:
def is_oauth_redirect(self, flow: http.HTTPFlow) -> bool:
code = flow.response.status_code
loc = self.get_header(flow.response.headers, "location") or ""
return 300 <= code < 400 and self.is_oauth_uri(loc)
return 300 <= code < 400 and is_oauth_uri(loc)
def check_nonce_in_request(self, flow: http.HTTPFlow) -> bool:
qs = parse_qs(urlparse(flow.request.url).query)
@ -71,10 +62,10 @@ class CsrfChecker:
headers=headers,
content=flow.request.get_content(),
)
def check_redirect_nonce(self, flow: http.HTTPFlow) -> Union[List[str], int]:
# ① OAuth URI, ② 요청에 nonce, ③ 리다이렉트 응답
if not (self.is_oauth_uri(flow.request.url)
if not (is_oauth_uri(flow.request.url)
and self.check_nonce_in_request(flow)
and self.is_oauth_redirect(flow)):
return 0
@ -85,18 +76,19 @@ class CsrfChecker:
resp_nonce = self.get_query_param(loc, param) if param else None
if resp_nonce is None:
return ["Missing nonce in redirect"]
report_vuln(title="CSRF Risk", desc="Missing nonce in redirect response", status="HIGH", uri=flow.request.url)
return 1
if orig_nonce != resp_nonce:
return ["Nonce mismatch request↔response"]
report_vuln(title="CSRF Risk", desc="Nonce mismatch request↔response", status="MEDIUM", uri=flow.request.url)
return 1
return 0
async def check_nonce_reuse(self, flow: http.HTTPFlow) -> Union[int, List[str]]:
# OAuth Request가 아니면서, OAuth 리다이렉트 응답인 경우만 검사
if self.is_oauth_uri(flow.request.url) or not self.is_oauth_redirect(flow):
if is_oauth_uri(flow.request.url) or not self.is_oauth_redirect(flow):
return 0
loc0 = self.get_header(flow.response.headers, "location") or ""
param = self.find_nonce_param(loc0) or "state"
qs0 = parse_qs(urlparse(loc0).query)
@ -111,7 +103,8 @@ class CsrfChecker:
if new_nonce is None:
return 0
if new_nonce == orig_nonce:
return ["Nonce reused without cookies"]
report_vuln(title="CSRF Risk", desc="Nonce reused without cookies", status="HIGH", uri=flow.request.url)
return 1
# (2) 두 번의 리다이렉트 비교
async with httpx.AsyncClient(follow_redirects=False) as cli:
@ -127,42 +120,25 @@ class CsrfChecker:
and urlparse(req1.headers.get("location", "")).path
== urlparse(req2.headers.get("location", "")).path
):
return ["Identical redirects on nonce swap → potential CSRF"]
report_vuln(title="CSRF Risk", desc="Identical redirects on nonce swap → potential CSRF", status="MEDIUM", uri=flow.request.url)
return 1
return 0
async def response(self, flow: http.HTTPFlow) -> None:
try:
msgs: List[str] = []
# 1) 요청에 nonce 없으면
if self.is_oauth_uri(flow.request.url) and not self.check_nonce_in_request(flow):
msgs.append("Missing state/nonce in request")
if is_oauth_uri(flow.request.url) and not self.check_nonce_in_request(flow):
report_vuln(title="CSRF Risk", desc="Missing nonce in OAuth request", status="HIGH", uri=flow.request.url)
return
# 2) 리다이렉트에서 nonce 검사
r1 = self.check_redirect_nonce(flow)
if r1:
msgs.extend(r1 if isinstance(r1, list) else [])
# 3) nonce 재사용 검사
r2 = await self.check_nonce_reuse(flow)
if r2:
msgs.extend(r2 if isinstance(r2, list) else [])
if msgs:
desc = " | ".join(msgs)
status = "MEDIUM"
report_data = [{
'target': cur_target_url.load(),
'status': status,
'title': "CSRF Risk",
'description': desc,
'uri': flow.request.url,
}]
save_report(report_data)
print(f"[INFO] CSRF Check: {desc}")
else:
pass
except Exception as e:
print(f"[ERROR] CSRF Check failed: {e}")
return