create-dwy 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +6 -15
- package/package.json +3 -3
- package/src/create.js +61 -37
- package/src/sync.js +206 -61
- package/templates/claude-global/CLAUDE.md +92 -0
- package/templates/claude-global/rules/dwy-android-core.md +610 -0
- package/templates/claude-global/rules/dwy-api-design.md +110 -0
- package/templates/claude-global/rules/dwy-db-migration.md +46 -0
- package/templates/claude-global/rules/dwy-flutter-core.md +564 -0
- package/templates/claude-global/rules/dwy-flutter-network.md +222 -0
- package/templates/claude-global/rules/dwy-flutter-router.md +97 -0
- package/templates/claude-global/rules/dwy-flutter-testing.md +94 -0
- package/templates/claude-global/rules/dwy-git-commit-convention.md +82 -0
- package/templates/claude-global/rules/dwy-git-security.md +53 -0
- package/templates/claude-global/rules/dwy-payload-limits.md +178 -0
- package/templates/claude-global/rules/{prefer-open-source.md → dwy-prefer-open-source.md} +4 -0
- package/templates/claude-global/rules/dwy-project-structure.md +181 -0
- package/templates/claude-global/rules/{python-code-style.md → dwy-python-core.md} +292 -358
- package/templates/claude-global/rules/dwy-python-fastapi.md +272 -0
- package/templates/claude-global/rules/dwy-python-orm.md +61 -0
- package/templates/claude-global/rules/{backend-security.md → dwy-python-security.md} +99 -72
- package/templates/claude-global/rules/dwy-python-testing.md +53 -0
- package/templates/claude-global/rules/{server-security.md → dwy-server-security.md} +1 -2
- package/templates/claude-global/rules/dwy-tdd-development.md +70 -0
- package/templates/claude-global/rules/dwy-tech-stack.md +60 -0
- package/templates/claude-global/rules/dwy-vue-api.md +146 -0
- package/templates/claude-global/rules/{vue-code-style.md → dwy-vue-core.md} +338 -152
- package/templates/claude-global/rules/dwy-vue-pinia.md +75 -0
- package/templates/claude-global/rules/dwy-vue-testing.md +102 -0
- package/templates/claude-global/skills/dwy-deploy-audit/SKILL.md +481 -0
- package/templates/claude-global/skills/dwy-deploy-audit/references/checks-data.md +117 -0
- package/templates/claude-global/skills/dwy-deploy-audit/references/checks-network.md +74 -0
- package/templates/claude-global/skills/dwy-deploy-audit/references/checks-runtime.md +184 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_base.sh +39 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_capacity.sh +278 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_db.sh +122 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_docker.sh +249 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_env.sh +64 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_https.sh +60 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_logs.sh +209 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_nginx.sh +92 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_resilience.sh +157 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_secrets.sh +327 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_services.sh +77 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_ssh.sh +56 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/run_all.sh +105 -0
- package/templates/claude-global/skills/dwy-docker/SKILL.md +187 -0
- package/templates/claude-global/skills/dwy-docker/references/ask-templates.md +142 -0
- package/templates/claude-global/skills/dwy-docker/references/templates.md +479 -0
- package/templates/claude-global/skills/dwy-docker/references/version-rules.md +76 -0
- package/templates/claude-global/skills/dwy-docker/scripts/query_dockerhub.py +204 -0
- package/templates/claude-global/skills/dwy-docker/scripts/scan_images.sh +185 -0
- package/templates/claude-global/skills/dwy-eapi/SKILL.md +433 -0
- package/templates/claude-global/skills/dwy-eapi/references/tasks-integration-guide.md +387 -0
- package/templates/claude-global/skills/dwy-ekit/SKILL.md +85 -0
- package/templates/claude-global/skills/dwy-ekit/references/data.md +135 -0
- package/templates/claude-global/skills/dwy-ekit/references/form.md +68 -0
- package/templates/claude-global/skills/dwy-ekit/references/request.md +183 -0
- package/templates/claude-global/skills/dwy-ekit/references/ui.md +109 -0
- package/templates/claude-global/skills/{dwy-frontend-eui → dwy-eui}/SKILL.md +129 -15
- package/templates/claude-global/skills/dwy-eui/references/eui-design-guide.md +257 -0
- package/templates/claude-global/skills/dwy-eui/references/eui-integration-guide.md +302 -0
- package/templates/claude-global/skills/dwy-eui/references/eui-landing-design-guide.md +437 -0
- package/templates/claude-global/skills/dwy-fullstack-scaffold/SKILL.md +214 -0
- package/templates/claude-global/skills/dwy-mirror-source/SKILL.md +275 -0
- package/templates/claude-global/skills/dwy-mirror-source/references/dockerfile-snippets.md +228 -0
- package/templates/claude-global/skills/dwy-mirror-source/references/mirror-providers.md +150 -0
- package/templates/claude-global/skills/dwy-mirror-source/scripts/__pycache__/check_mirrors.cpython-313.pyc +0 -0
- package/templates/claude-global/skills/dwy-mirror-source/scripts/apply_mirrors.py +666 -0
- package/templates/claude-global/skills/dwy-mirror-source/scripts/check_mirrors.py +868 -0
- package/templates/claude-global/skills/dwy-pentest/SKILL.md +344 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__init__.py +1 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/__init__.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/config.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/executor.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/severity.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/validator.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/config.py +109 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/executor.py +157 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/severity.py +96 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/validator.py +113 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_auth.py +289 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_recon.py +217 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_report.py +343 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_scan.py +205 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_ssl.py +208 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_vuln.py +205 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_web.py +330 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pyproject.toml +14 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/setup_tools.sh +97 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/uv.lock +415 -0
- package/templates/claude-global/skills/{dwy-release → dwy-publish}/SKILL.md +42 -18
- package/templates/claude-global/skills/dwy-vue-core/SKILL.md +890 -0
- package/templates/project/backend/.dwy.hbs +5 -0
- package/templates/project/backend/.python-version +1 -0
- package/templates/project/backend/CLAUDE.md.hbs +40 -0
- package/templates/project/{web/backend → backend}/alembic/script.py.mako +2 -0
- package/templates/project/{web/backend → backend}/alembic.ini.hbs +0 -1
- package/templates/project/backend/deploy.sh.hbs +18 -0
- package/templates/project/{web → backend}/docker-compose.prod.yml.hbs +1 -1
- package/templates/project/{web/backend → backend}/pyproject.toml.hbs +4 -5
- package/templates/project/{web/backend → backend}/src/app/config.py.hbs +1 -1
- package/templates/project/backend/src/app/database.py +10 -0
- package/templates/project/backend/src/app/dependencies.py +5 -0
- package/templates/project/{web/backend → backend}/src/app/main.py.hbs +3 -3
- package/templates/project/backend/src/app/models/base.py +5 -0
- package/templates/project/web/CLAUDE.md.hbs +29 -36
- package/templates/project/web/{frontend → apps/web}/index.html.hbs +1 -0
- package/templates/project/web/{frontend → apps/web}/package.json.hbs +4 -3
- package/templates/project/web/apps/web/src/App.vue +9 -0
- package/templates/project/web/apps/web/src/api/client.ts +5 -0
- package/templates/project/web/apps/web/src/composables/.gitkeep +0 -0
- package/templates/project/web/apps/web/src/layouts/DefaultLayout.vue +8 -0
- package/templates/project/web/{frontend → apps/web}/src/router/index.ts +1 -1
- package/templates/project/web/{frontend → apps/web}/src/stores/auth.ts +4 -2
- package/templates/project/web/apps/web/src/style.css +2 -0
- package/templates/project/web/apps/web/src/types/router.d.ts +8 -0
- package/templates/project/web/apps/web/src/views/HomeView.vue +8 -0
- package/templates/project/web/apps/web/tsconfig.app.json +18 -0
- package/templates/project/web/apps/web/tsconfig.json +6 -0
- package/templates/project/web/{frontend → apps/web}/vite.config.ts.hbs +7 -1
- package/templates/project/web/package.json.hbs +16 -0
- package/templates/project/web/packages/.gitkeep +0 -0
- package/templates/project/web/pnpm-workspace.yaml +3 -0
- package/templates/claude-global/hooks/post-sync-claude-global.sh +0 -72
- package/templates/claude-global/rules/agent-team-execution.md +0 -138
- package/templates/claude-global/rules/agent-team-patterns.md +0 -157
- package/templates/claude-global/rules/git-commit-convention.md +0 -106
- package/templates/claude-global/rules/git-security.md +0 -144
- package/templates/claude-global/skills/dwy-backend-base/SKILL.md +0 -279
- package/templates/claude-global/skills/dwy-frontend-core/SKILL.md +0 -181
- package/templates/project/web/.claude/commands/deploy.md +0 -4
- package/templates/project/web/backend/src/app/models/base.py +0 -5
- package/templates/project/web/deploy.sh.hbs +0 -113
- package/templates/project/web/frontend/src/App.vue +0 -3
- package/templates/project/web/frontend/src/api/client.ts +0 -13
- package/templates/project/web/frontend/src/style.css +0 -1
- package/templates/project/web/frontend/src/views/HomeView.vue +0 -5
- package/templates/project/web/frontend/tsconfig.app.json +0 -14
- package/templates/project/web/frontend/tsconfig.json +0 -4
- /package/templates/{project/web/backend → claude-global/skills/dwy-pentest/scripts}/.python-version +0 -0
- /package/templates/project/{web/backend → backend}/Dockerfile +0 -0
- /package/templates/project/{web/backend → backend}/alembic/env.py.hbs +0 -0
- /package/templates/{claude-global/skills → project/backend/alembic/versions}/.gitkeep +0 -0
- /package/templates/project/{web → backend}/docker-compose.dev.yml.hbs +0 -0
- /package/templates/project/{web/backend → backend}/src/app/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/models/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/routers/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/routers/health.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/schemas/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/services/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/tests/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/tests/conftest.py.hbs +0 -0
- /package/templates/project/{web/backend/alembic/versions → mobile}/.gitkeep +0 -0
- /package/templates/project/web/{frontend → apps/web}/src/components/.gitkeep +0 -0
- /package/templates/project/web/{frontend → apps/web}/src/main.ts +0 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""阶段 4:Web 应用测试 — 目录枚举、SQL 注入探测、XSS 探测(HTTP 探测全异步并发)。
|
|
2
|
+
|
|
3
|
+
用法:
|
|
4
|
+
uv run pentest_web.py --target <url> --mode dir|sqli|xss --output-dir <dir>
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import asyncio
|
|
9
|
+
import sys
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
|
|
16
|
+
from lib.executor import AuditLog, run_command, save_json_result
|
|
17
|
+
from lib.validator import validate_target
|
|
18
|
+
|
|
19
|
+
console = Console()
|
|
20
|
+
|
|
21
|
+
XSS_PROBE = "<dwy-xss-test>"
|
|
22
|
+
XSS_PAYLOADS = [
|
|
23
|
+
'<dwy-xss-test>',
|
|
24
|
+
'"><dwy-xss-test>',
|
|
25
|
+
"'-dwy-xss-test-'",
|
|
26
|
+
'javascript:dwy-xss-test',
|
|
27
|
+
'<img src=x onerror=dwy-xss-test>',
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
COMMON_PATHS = [
|
|
31
|
+
"/admin", "/login", "/api", "/backup", "/.env", "/.git/HEAD",
|
|
32
|
+
"/.htaccess", "/wp-admin", "/wp-login.php", "/phpmyadmin",
|
|
33
|
+
"/robots.txt", "/sitemap.xml", "/server-status", "/server-info",
|
|
34
|
+
"/.DS_Store", "/web.config", "/crossdomain.xml", "/favicon.ico",
|
|
35
|
+
"/api/v1", "/api/docs", "/swagger.json", "/openapi.json",
|
|
36
|
+
"/graphql", "/.well-known/security.txt", "/config.json",
|
|
37
|
+
"/package.json", "/composer.json", "/Dockerfile",
|
|
38
|
+
]
|
|
39
|
+
SENSITIVE_PATTERNS = [".env", ".git", ".htaccess", "backup", ".sql", ".bak", "config", "wp-admin", "phpmyadmin"]
|
|
40
|
+
SQLI_PROBES = ["'", "\"", "' OR '1'='1", "1' OR '1'='1' --", "1 UNION SELECT NULL--"]
|
|
41
|
+
SQL_ERROR_PATTERNS = [
|
|
42
|
+
"SQL syntax", "mysql_fetch", "ORA-", "PostgreSQL",
|
|
43
|
+
"SQLite", "SQLITE_ERROR", "Unclosed quotation",
|
|
44
|
+
"unterminated string", "syntax error",
|
|
45
|
+
]
|
|
46
|
+
SECURITY_HEADERS = {
|
|
47
|
+
"X-Content-Type-Options": "防止 MIME 嗅探",
|
|
48
|
+
"X-Frame-Options": "防止点击劫持",
|
|
49
|
+
"Content-Security-Policy": "内容安全策略",
|
|
50
|
+
"X-XSS-Protection": "XSS 过滤",
|
|
51
|
+
"Strict-Transport-Security": "HSTS",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def _async_probe_path(client: httpx.AsyncClient, base_url: str, path: str) -> dict | None:
|
|
56
|
+
try:
|
|
57
|
+
resp = await client.get(f"{base_url}{path}")
|
|
58
|
+
if resp.status_code in (200, 201, 204, 301, 302, 307, 401, 403):
|
|
59
|
+
return {
|
|
60
|
+
"path": path,
|
|
61
|
+
"status": resp.status_code,
|
|
62
|
+
"size": len(resp.content),
|
|
63
|
+
"redirect": resp.headers.get("location", ""),
|
|
64
|
+
}
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
async def _async_enum_paths(url: str, paths: list[str]) -> list[dict]:
|
|
71
|
+
"""并发探测路径列表。"""
|
|
72
|
+
limits = httpx.Limits(max_connections=20, max_keepalive_connections=10)
|
|
73
|
+
async with httpx.AsyncClient(timeout=5, verify=False, follow_redirects=False, limits=limits) as client:
|
|
74
|
+
tasks = [_async_probe_path(client, url, p) for p in paths]
|
|
75
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
76
|
+
return [r for r in results if isinstance(r, dict)]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def scan_directory(url: str, audit: AuditLog, output_dir: Path) -> list[dict]:
|
|
80
|
+
"""目录/文件枚举(gobuster → ffuf → 内置并发探测)。"""
|
|
81
|
+
findings = []
|
|
82
|
+
console.print(f"[bold]目录枚举: {url}[/bold]")
|
|
83
|
+
|
|
84
|
+
gobuster_output = output_dir / "gobuster_raw.txt"
|
|
85
|
+
cmd = [
|
|
86
|
+
"gobuster", "dir",
|
|
87
|
+
"-u", url,
|
|
88
|
+
"-w", "/usr/share/wordlists/dirb/common.txt",
|
|
89
|
+
"-o", str(gobuster_output),
|
|
90
|
+
"-t", "20",
|
|
91
|
+
"-q",
|
|
92
|
+
"--no-error",
|
|
93
|
+
"-s", "200,201,204,301,302,307,401,403",
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
result = run_command(cmd, timeout=300, audit=audit)
|
|
97
|
+
|
|
98
|
+
if result.returncode == -3:
|
|
99
|
+
console.print("[yellow]gobuster 未安装,尝试 ffuf...[/yellow]")
|
|
100
|
+
ffuf_output = output_dir / "ffuf_raw.json"
|
|
101
|
+
cmd = [
|
|
102
|
+
"ffuf",
|
|
103
|
+
"-u", f"{url}/FUZZ",
|
|
104
|
+
"-w", "/usr/share/wordlists/dirb/common.txt",
|
|
105
|
+
"-o", str(ffuf_output),
|
|
106
|
+
"-of", "json",
|
|
107
|
+
"-t", "20",
|
|
108
|
+
"-mc", "200,201,204,301,302,307,401,403",
|
|
109
|
+
"-s",
|
|
110
|
+
]
|
|
111
|
+
result = run_command(cmd, timeout=300, audit=audit)
|
|
112
|
+
|
|
113
|
+
if result.returncode == -3:
|
|
114
|
+
console.print("[yellow]gobuster/ffuf 均未安装,使用内置路径列表(并发)...[/yellow]")
|
|
115
|
+
findings = asyncio.run(_async_enum_paths(url, COMMON_PATHS))
|
|
116
|
+
elif result.returncode == 0 and gobuster_output.exists():
|
|
117
|
+
with open(gobuster_output) as f:
|
|
118
|
+
for line in f:
|
|
119
|
+
parts = line.strip().split()
|
|
120
|
+
if parts:
|
|
121
|
+
findings.append({
|
|
122
|
+
"path": parts[0],
|
|
123
|
+
"status": int(parts[1].strip("()")) if len(parts) > 1 else 200,
|
|
124
|
+
"size": 0,
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
for f in findings:
|
|
128
|
+
f["sensitive"] = any(p in f["path"].lower() for p in SENSITIVE_PATTERNS)
|
|
129
|
+
if f["sensitive"]:
|
|
130
|
+
console.print(f" [red][敏感][/red] {f['path']} — {f['status']}")
|
|
131
|
+
else:
|
|
132
|
+
console.print(f" [green]{f['path']}[/green] — {f['status']}")
|
|
133
|
+
|
|
134
|
+
console.print(f" 发现 {len(findings)} 个路径")
|
|
135
|
+
return findings
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def scan_sqli(url: str, audit: AuditLog, output_dir: Path) -> list[dict]:
|
|
139
|
+
"""SQL 注入探测(sqlmap,仅检测不利用)。"""
|
|
140
|
+
findings = []
|
|
141
|
+
console.print(f"[bold]SQL 注入探测: {url}[/bold]")
|
|
142
|
+
|
|
143
|
+
sqlmap_output = output_dir / "sqlmap_output"
|
|
144
|
+
cmd = [
|
|
145
|
+
"sqlmap",
|
|
146
|
+
"-u", url,
|
|
147
|
+
"--batch",
|
|
148
|
+
"--level", "3",
|
|
149
|
+
"--risk", "2",
|
|
150
|
+
"--technique=BEU",
|
|
151
|
+
"--output-dir", str(sqlmap_output),
|
|
152
|
+
"--forms",
|
|
153
|
+
"--crawl=2",
|
|
154
|
+
"--threads=4",
|
|
155
|
+
"--random-agent",
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
result = run_command(cmd, timeout=600, audit=audit)
|
|
159
|
+
|
|
160
|
+
if result.blocked:
|
|
161
|
+
console.print(f"[red]sqlmap 被安全策略拦截: {result.block_reason}[/red]")
|
|
162
|
+
return findings
|
|
163
|
+
|
|
164
|
+
if result.returncode == -3:
|
|
165
|
+
console.print("[yellow]sqlmap 未安装,使用基础 SQL 注入并发探针...[/yellow]")
|
|
166
|
+
return asyncio.run(_async_basic_sqli_probe(url))
|
|
167
|
+
|
|
168
|
+
if result.returncode == 0:
|
|
169
|
+
output_text = result.stdout
|
|
170
|
+
if "is vulnerable" in output_text.lower() or "injectable" in output_text.lower():
|
|
171
|
+
for line in output_text.split("\n"):
|
|
172
|
+
if "Parameter:" in line or "injectable" in line.lower():
|
|
173
|
+
findings.append({
|
|
174
|
+
"type": "SQL Injection",
|
|
175
|
+
"detail": line.strip(),
|
|
176
|
+
"url": url,
|
|
177
|
+
"severity": "High",
|
|
178
|
+
"cvss": 8.0,
|
|
179
|
+
})
|
|
180
|
+
console.print(f" [red][SQLi] {line.strip()}[/red]")
|
|
181
|
+
|
|
182
|
+
if not findings:
|
|
183
|
+
console.print(" [green]未发现 SQL 注入点[/green]")
|
|
184
|
+
|
|
185
|
+
return findings
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
async def _async_sqli_one(client: httpx.AsyncClient, url: str, probe: str) -> dict | None:
|
|
189
|
+
test_url = f"{url}{probe}" if "?" in url else f"{url}?id={probe}"
|
|
190
|
+
try:
|
|
191
|
+
resp = await client.get(test_url)
|
|
192
|
+
for pattern in SQL_ERROR_PATTERNS:
|
|
193
|
+
if pattern.lower() in resp.text.lower():
|
|
194
|
+
return {
|
|
195
|
+
"type": "SQL Injection (Basic Probe)",
|
|
196
|
+
"detail": f"SQL 错误特征 '{pattern}' 在探针 '{probe}' 后出现",
|
|
197
|
+
"url": test_url,
|
|
198
|
+
"severity": "High",
|
|
199
|
+
"cvss": 8.0,
|
|
200
|
+
"_pattern": pattern,
|
|
201
|
+
}
|
|
202
|
+
except Exception:
|
|
203
|
+
return None
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
async def _async_basic_sqli_probe(url: str) -> list[dict]:
|
|
208
|
+
"""基础 SQL 注入并发探针。"""
|
|
209
|
+
findings: list[dict] = []
|
|
210
|
+
async with httpx.AsyncClient(timeout=10, verify=False, follow_redirects=True) as client:
|
|
211
|
+
tasks = [_async_sqli_one(client, url, probe) for probe in SQLI_PROBES]
|
|
212
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
213
|
+
|
|
214
|
+
for r in results:
|
|
215
|
+
if isinstance(r, dict):
|
|
216
|
+
console.print(f" [red][SQLi] 发现 SQL 错误: {r['_pattern']}[/red]")
|
|
217
|
+
r.pop("_pattern", None)
|
|
218
|
+
findings.append(r)
|
|
219
|
+
return findings
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
async def _async_xss_one(client: httpx.AsyncClient, url: str, payload: str) -> dict | None:
|
|
223
|
+
test_url = f"{url}&xss_test={payload}" if "?" in url else f"{url}?q={payload}"
|
|
224
|
+
try:
|
|
225
|
+
resp = await client.get(test_url)
|
|
226
|
+
if XSS_PROBE in resp.text or "dwy-xss-test" in resp.text:
|
|
227
|
+
return {
|
|
228
|
+
"type": "Reflected XSS",
|
|
229
|
+
"detail": f"探针 '{payload}' 被反射到响应中",
|
|
230
|
+
"url": test_url,
|
|
231
|
+
"severity": "Medium",
|
|
232
|
+
"cvss": 6.1,
|
|
233
|
+
}
|
|
234
|
+
except Exception:
|
|
235
|
+
return None
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
async def _async_check_security_headers(client: httpx.AsyncClient, url: str) -> list[dict]:
|
|
240
|
+
findings: list[dict] = []
|
|
241
|
+
try:
|
|
242
|
+
resp = await client.get(url)
|
|
243
|
+
headers_lower = {k.lower() for k in resp.headers.keys()}
|
|
244
|
+
for header, desc in SECURITY_HEADERS.items():
|
|
245
|
+
if header.lower() not in headers_lower:
|
|
246
|
+
findings.append({
|
|
247
|
+
"type": "Missing Security Header",
|
|
248
|
+
"detail": f"缺少 {header} 头({desc})",
|
|
249
|
+
"url": url,
|
|
250
|
+
"severity": "Low",
|
|
251
|
+
"cvss": 2.0,
|
|
252
|
+
})
|
|
253
|
+
except Exception:
|
|
254
|
+
pass
|
|
255
|
+
return findings
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
async def _async_xss_scan(url: str) -> list[dict]:
|
|
259
|
+
findings: list[dict] = []
|
|
260
|
+
async with httpx.AsyncClient(timeout=10, verify=False, follow_redirects=True) as client:
|
|
261
|
+
# XSS payload 探测 + 安全头检查 全部并发
|
|
262
|
+
payload_tasks = [_async_xss_one(client, url, p) for p in XSS_PAYLOADS]
|
|
263
|
+
header_task = _async_check_security_headers(client, url)
|
|
264
|
+
results = await asyncio.gather(*payload_tasks, header_task, return_exceptions=True)
|
|
265
|
+
|
|
266
|
+
for r in results[:-1]:
|
|
267
|
+
if isinstance(r, dict):
|
|
268
|
+
console.print(f" [yellow][XSS] 反射探针被检测到: {r['detail']}[/yellow]")
|
|
269
|
+
findings.append(r)
|
|
270
|
+
|
|
271
|
+
header_findings = results[-1] if isinstance(results[-1], list) else []
|
|
272
|
+
for h in header_findings:
|
|
273
|
+
console.print(f" [blue][Header] {h['detail']}[/blue]")
|
|
274
|
+
findings.append(h)
|
|
275
|
+
|
|
276
|
+
return findings
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def scan_xss(url: str, audit: AuditLog) -> list[dict]:
|
|
280
|
+
"""XSS 反射探测(payload + 安全头并发)。"""
|
|
281
|
+
console.print(f"[bold]XSS 反射探测: {url}[/bold]")
|
|
282
|
+
findings = asyncio.run(_async_xss_scan(url))
|
|
283
|
+
if not findings:
|
|
284
|
+
console.print(" [green]未发现 XSS 反射点[/green]")
|
|
285
|
+
return findings
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def main():
|
|
289
|
+
parser = argparse.ArgumentParser(description="渗透测试 - Web 应用测试(并发)")
|
|
290
|
+
parser.add_argument("--target", required=True, help="目标 URL")
|
|
291
|
+
parser.add_argument("--mode", required=True, choices=["dir", "sqli", "xss"], help="测试模式")
|
|
292
|
+
parser.add_argument("--output-dir", required=True, help="输出目录")
|
|
293
|
+
args = parser.parse_args()
|
|
294
|
+
|
|
295
|
+
target, warning = validate_target(args.target)
|
|
296
|
+
if target is None:
|
|
297
|
+
console.print(f"[red]{warning}[/red]")
|
|
298
|
+
sys.exit(1)
|
|
299
|
+
if warning:
|
|
300
|
+
console.print(f"[yellow]{warning}[/yellow]")
|
|
301
|
+
|
|
302
|
+
output_dir = Path(args.output_dir)
|
|
303
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
304
|
+
audit = AuditLog(output_dir / "audit.log")
|
|
305
|
+
|
|
306
|
+
url = target.web_url or f"http://{target.host}"
|
|
307
|
+
findings = []
|
|
308
|
+
|
|
309
|
+
if args.mode == "dir":
|
|
310
|
+
findings = scan_directory(url, audit, output_dir)
|
|
311
|
+
elif args.mode == "sqli":
|
|
312
|
+
findings = scan_sqli(url, audit, output_dir)
|
|
313
|
+
elif args.mode == "xss":
|
|
314
|
+
findings = scan_xss(url, audit)
|
|
315
|
+
|
|
316
|
+
result = {
|
|
317
|
+
"target": url,
|
|
318
|
+
"mode": args.mode,
|
|
319
|
+
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
320
|
+
"findings": findings,
|
|
321
|
+
"total": len(findings),
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
output_file = output_dir / f"web_{args.mode}.json"
|
|
325
|
+
save_json_result(result, output_file)
|
|
326
|
+
console.print(f"\n[green]结果已保存: {output_file}[/green]")
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
if __name__ == "__main__":
|
|
330
|
+
main()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dwy-pentest"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "White-hat penetration testing scripts for Claude Code skill"
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"python-nmap>=0.7.1",
|
|
8
|
+
"httpx>=0.27.0",
|
|
9
|
+
"dnspython>=2.6.0",
|
|
10
|
+
"python-whois>=0.9.4",
|
|
11
|
+
"rich>=13.0.0",
|
|
12
|
+
"jinja2>=3.1.0",
|
|
13
|
+
"pydantic>=2.0.0",
|
|
14
|
+
]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# setup_tools.sh — 检测并安装渗透测试所需的 CLI 工具
|
|
3
|
+
# 仅支持 macOS (brew)
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
RED='\033[0;31m'
|
|
8
|
+
GREEN='\033[0;32m'
|
|
9
|
+
YELLOW='\033[1;33m'
|
|
10
|
+
NC='\033[0m'
|
|
11
|
+
|
|
12
|
+
# 工具列表:命令名 → brew 包名
|
|
13
|
+
declare -A TOOLS=(
|
|
14
|
+
["nmap"]="nmap"
|
|
15
|
+
["nuclei"]="nuclei"
|
|
16
|
+
["testssl"]="testssl"
|
|
17
|
+
["gobuster"]="gobuster"
|
|
18
|
+
["sqlmap"]="sqlmap"
|
|
19
|
+
["hydra"]="hydra"
|
|
20
|
+
["ffuf"]="ffuf"
|
|
21
|
+
["whatweb"]="whatweb"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
echo "========================================="
|
|
25
|
+
echo " dwy-pentest 环境检测与安装"
|
|
26
|
+
echo "========================================="
|
|
27
|
+
echo ""
|
|
28
|
+
|
|
29
|
+
# 检查 brew
|
|
30
|
+
if ! command -v brew &> /dev/null; then
|
|
31
|
+
echo -e "${RED}[ERROR] Homebrew 未安装,请先安装: https://brew.sh${NC}"
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
MISSING=()
|
|
36
|
+
INSTALLED=()
|
|
37
|
+
|
|
38
|
+
for cmd in "${!TOOLS[@]}"; do
|
|
39
|
+
if command -v "$cmd" &> /dev/null; then
|
|
40
|
+
version=$($cmd --version 2>/dev/null | head -1 || echo "已安装")
|
|
41
|
+
echo -e "${GREEN}[OK]${NC} $cmd — $version"
|
|
42
|
+
INSTALLED+=("$cmd")
|
|
43
|
+
else
|
|
44
|
+
echo -e "${YELLOW}[MISSING]${NC} $cmd"
|
|
45
|
+
MISSING+=("$cmd")
|
|
46
|
+
fi
|
|
47
|
+
done
|
|
48
|
+
|
|
49
|
+
echo ""
|
|
50
|
+
|
|
51
|
+
if [ ${#MISSING[@]} -eq 0 ]; then
|
|
52
|
+
echo -e "${GREEN}所有工具已就绪!${NC}"
|
|
53
|
+
else
|
|
54
|
+
echo -e "${YELLOW}缺少 ${#MISSING[@]} 个工具,正在安装...${NC}"
|
|
55
|
+
echo ""
|
|
56
|
+
|
|
57
|
+
for cmd in "${MISSING[@]}"; do
|
|
58
|
+
pkg="${TOOLS[$cmd]}"
|
|
59
|
+
echo -e " 安装 $pkg ..."
|
|
60
|
+
brew install "$pkg" 2>/dev/null || echo -e "${RED} [WARN] $pkg 安装失败,请手动安装${NC}"
|
|
61
|
+
done
|
|
62
|
+
|
|
63
|
+
echo ""
|
|
64
|
+
echo "安装完成,重新检测:"
|
|
65
|
+
for cmd in "${MISSING[@]}"; do
|
|
66
|
+
if command -v "$cmd" &> /dev/null; then
|
|
67
|
+
echo -e " ${GREEN}[OK]${NC} $cmd"
|
|
68
|
+
else
|
|
69
|
+
echo -e " ${RED}[FAIL]${NC} $cmd — 请手动安装"
|
|
70
|
+
fi
|
|
71
|
+
done
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# 更新 nuclei 模板
|
|
75
|
+
if command -v nuclei &> /dev/null; then
|
|
76
|
+
echo ""
|
|
77
|
+
echo "更新 nuclei 漏洞模板..."
|
|
78
|
+
nuclei -update-templates 2>/dev/null || echo -e "${YELLOW}[WARN] nuclei 模板更新失败,可能需要网络${NC}"
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
# 检查 Python 环境
|
|
82
|
+
echo ""
|
|
83
|
+
echo "检查 Python 环境..."
|
|
84
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
85
|
+
|
|
86
|
+
if command -v uv &> /dev/null; then
|
|
87
|
+
echo "使用 uv 同步 Python 依赖..."
|
|
88
|
+
cd "$SCRIPT_DIR" && uv sync 2>/dev/null || echo -e "${YELLOW}[WARN] uv sync 失败${NC}"
|
|
89
|
+
echo -e "${GREEN}Python 环境就绪${NC}"
|
|
90
|
+
else
|
|
91
|
+
echo -e "${YELLOW}[WARN] uv 未安装,请安装: pip install uv${NC}"
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
echo ""
|
|
95
|
+
echo "========================================="
|
|
96
|
+
echo -e "${GREEN} 环境准备完成${NC}"
|
|
97
|
+
echo "========================================="
|