create-dwy 0.5.0 → 0.6.0

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.
Files changed (156) hide show
  1. package/bin/index.js +1 -1
  2. package/package.json +2 -2
  3. package/src/create.js +61 -37
  4. package/src/sync.js +213 -53
  5. package/templates/claude-global/CLAUDE.md +92 -0
  6. package/templates/claude-global/rules/dwy-android-core.md +610 -0
  7. package/templates/claude-global/rules/dwy-api-design.md +110 -0
  8. package/templates/claude-global/rules/dwy-db-migration.md +46 -0
  9. package/templates/claude-global/rules/dwy-flutter-core.md +564 -0
  10. package/templates/claude-global/rules/dwy-flutter-network.md +222 -0
  11. package/templates/claude-global/rules/dwy-flutter-router.md +97 -0
  12. package/templates/claude-global/rules/dwy-flutter-testing.md +94 -0
  13. package/templates/claude-global/rules/dwy-git-commit-convention.md +82 -0
  14. package/templates/claude-global/rules/dwy-git-security.md +53 -0
  15. package/templates/claude-global/rules/dwy-payload-limits.md +178 -0
  16. package/templates/claude-global/rules/{prefer-open-source.md → dwy-prefer-open-source.md} +4 -0
  17. package/templates/claude-global/rules/dwy-project-structure.md +181 -0
  18. package/templates/claude-global/rules/{python-code-style.md → dwy-python-core.md} +314 -357
  19. package/templates/claude-global/rules/dwy-python-fastapi.md +272 -0
  20. package/templates/claude-global/rules/dwy-python-orm.md +61 -0
  21. package/templates/claude-global/rules/{backend-security.md → dwy-python-security.md} +116 -79
  22. package/templates/claude-global/rules/dwy-python-testing.md +53 -0
  23. package/templates/claude-global/rules/{server-security.md → dwy-server-security.md} +1 -2
  24. package/templates/claude-global/rules/dwy-tdd-development.md +70 -0
  25. package/templates/claude-global/rules/dwy-tech-stack.md +60 -0
  26. package/templates/claude-global/rules/dwy-vue-api.md +146 -0
  27. package/templates/claude-global/rules/{vue-code-style.md → dwy-vue-core.md} +361 -152
  28. package/templates/claude-global/rules/dwy-vue-pinia.md +75 -0
  29. package/templates/claude-global/rules/dwy-vue-testing.md +102 -0
  30. package/templates/claude-global/settings.json +20 -0
  31. package/templates/claude-global/skills/dwy-deploy-audit/SKILL.md +481 -0
  32. package/templates/claude-global/skills/dwy-deploy-audit/references/checks-data.md +117 -0
  33. package/templates/claude-global/skills/dwy-deploy-audit/references/checks-network.md +74 -0
  34. package/templates/claude-global/skills/dwy-deploy-audit/references/checks-runtime.md +184 -0
  35. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_base.sh +39 -0
  36. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_capacity.sh +278 -0
  37. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_db.sh +122 -0
  38. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_docker.sh +249 -0
  39. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_env.sh +64 -0
  40. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_https.sh +60 -0
  41. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_logs.sh +209 -0
  42. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_nginx.sh +92 -0
  43. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_resilience.sh +157 -0
  44. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_secrets.sh +327 -0
  45. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_services.sh +77 -0
  46. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_ssh.sh +56 -0
  47. package/templates/claude-global/skills/dwy-deploy-audit/scripts/run_all.sh +105 -0
  48. package/templates/claude-global/skills/dwy-docker/SKILL.md +187 -0
  49. package/templates/claude-global/skills/dwy-docker/references/ask-templates.md +142 -0
  50. package/templates/claude-global/skills/dwy-docker/references/templates.md +479 -0
  51. package/templates/claude-global/skills/dwy-docker/references/version-rules.md +76 -0
  52. package/templates/claude-global/skills/dwy-docker/scripts/query_dockerhub.py +204 -0
  53. package/templates/claude-global/skills/dwy-docker/scripts/scan_images.sh +185 -0
  54. package/templates/claude-global/skills/dwy-eapi/SKILL.md +433 -0
  55. package/templates/claude-global/skills/dwy-eapi/references/tasks-integration-guide.md +387 -0
  56. package/templates/claude-global/skills/dwy-ekit/SKILL.md +85 -0
  57. package/templates/claude-global/skills/dwy-ekit/references/data.md +135 -0
  58. package/templates/claude-global/skills/dwy-ekit/references/form.md +68 -0
  59. package/templates/claude-global/skills/dwy-ekit/references/request.md +183 -0
  60. package/templates/claude-global/skills/dwy-ekit/references/ui.md +109 -0
  61. package/templates/claude-global/skills/{dwy-frontend-eui → dwy-eui}/SKILL.md +129 -15
  62. package/templates/claude-global/skills/dwy-eui/references/eui-design-guide.md +257 -0
  63. package/templates/claude-global/skills/dwy-eui/references/eui-integration-guide.md +302 -0
  64. package/templates/claude-global/skills/dwy-eui/references/eui-landing-design-guide.md +437 -0
  65. package/templates/claude-global/skills/dwy-fullstack-scaffold/SKILL.md +214 -0
  66. package/templates/claude-global/skills/dwy-mirror-source/SKILL.md +275 -0
  67. package/templates/claude-global/skills/dwy-mirror-source/references/dockerfile-snippets.md +228 -0
  68. package/templates/claude-global/skills/dwy-mirror-source/references/mirror-providers.md +150 -0
  69. package/templates/claude-global/skills/dwy-mirror-source/scripts/__pycache__/check_mirrors.cpython-313.pyc +0 -0
  70. package/templates/claude-global/skills/dwy-mirror-source/scripts/apply_mirrors.py +666 -0
  71. package/templates/claude-global/skills/dwy-mirror-source/scripts/check_mirrors.py +868 -0
  72. package/templates/claude-global/skills/dwy-pentest/SKILL.md +344 -0
  73. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__init__.py +1 -0
  74. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/__init__.cpython-311.pyc +0 -0
  75. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/config.cpython-311.pyc +0 -0
  76. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/executor.cpython-311.pyc +0 -0
  77. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/severity.cpython-311.pyc +0 -0
  78. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/validator.cpython-311.pyc +0 -0
  79. package/templates/claude-global/skills/dwy-pentest/scripts/lib/config.py +109 -0
  80. package/templates/claude-global/skills/dwy-pentest/scripts/lib/executor.py +157 -0
  81. package/templates/claude-global/skills/dwy-pentest/scripts/lib/severity.py +96 -0
  82. package/templates/claude-global/skills/dwy-pentest/scripts/lib/validator.py +113 -0
  83. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_auth.py +289 -0
  84. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_recon.py +217 -0
  85. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_report.py +343 -0
  86. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_scan.py +205 -0
  87. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_ssl.py +208 -0
  88. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_vuln.py +205 -0
  89. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_web.py +330 -0
  90. package/templates/claude-global/skills/dwy-pentest/scripts/pyproject.toml +14 -0
  91. package/templates/claude-global/skills/dwy-pentest/scripts/setup_tools.sh +97 -0
  92. package/templates/claude-global/skills/dwy-pentest/scripts/uv.lock +415 -0
  93. package/templates/claude-global/skills/dwy-publish/SKILL.md +135 -0
  94. package/templates/claude-global/skills/dwy-vue-core/SKILL.md +890 -0
  95. package/templates/project/backend/.dwy.hbs +5 -0
  96. package/templates/project/backend/.python-version +1 -0
  97. package/templates/project/backend/CLAUDE.md.hbs +40 -0
  98. package/templates/project/{web/backend → backend}/alembic/script.py.mako +2 -0
  99. package/templates/project/{web/backend → backend}/alembic.ini.hbs +0 -1
  100. package/templates/project/backend/deploy.sh.hbs +18 -0
  101. package/templates/project/{web → backend}/docker-compose.prod.yml.hbs +1 -1
  102. package/templates/project/{web/backend → backend}/pyproject.toml.hbs +4 -5
  103. package/templates/project/{web/backend → backend}/src/app/config.py.hbs +1 -1
  104. package/templates/project/backend/src/app/database.py +10 -0
  105. package/templates/project/backend/src/app/dependencies.py +5 -0
  106. package/templates/project/{web/backend → backend}/src/app/main.py.hbs +3 -3
  107. package/templates/project/backend/src/app/models/base.py +5 -0
  108. package/templates/project/web/CLAUDE.md.hbs +29 -36
  109. package/templates/project/web/{frontend → apps/web}/index.html.hbs +1 -0
  110. package/templates/project/web/{frontend → apps/web}/package.json.hbs +4 -3
  111. package/templates/project/web/apps/web/src/App.vue +9 -0
  112. package/templates/project/web/apps/web/src/api/client.ts +5 -0
  113. package/templates/project/web/apps/web/src/composables/.gitkeep +0 -0
  114. package/templates/project/web/apps/web/src/layouts/DefaultLayout.vue +8 -0
  115. package/templates/project/web/{frontend → apps/web}/src/router/index.ts +1 -1
  116. package/templates/project/web/{frontend → apps/web}/src/stores/auth.ts +4 -2
  117. package/templates/project/web/apps/web/src/style.css +2 -0
  118. package/templates/project/web/apps/web/src/types/router.d.ts +8 -0
  119. package/templates/project/web/apps/web/src/views/HomeView.vue +8 -0
  120. package/templates/project/web/apps/web/tsconfig.app.json +18 -0
  121. package/templates/project/web/apps/web/tsconfig.json +6 -0
  122. package/templates/project/web/{frontend → apps/web}/vite.config.ts.hbs +7 -1
  123. package/templates/project/web/package.json.hbs +16 -0
  124. package/templates/project/web/packages/.gitkeep +0 -0
  125. package/templates/project/web/pnpm-workspace.yaml +3 -0
  126. package/templates/claude-global/rules/agent-team-execution.md +0 -138
  127. package/templates/claude-global/rules/agent-team-patterns.md +0 -157
  128. package/templates/claude-global/rules/git-security.md +0 -144
  129. package/templates/claude-global/skills/dwy-auto-sync/SKILL.md +0 -52
  130. package/templates/claude-global/skills/dwy-backend-base/SKILL.md +0 -279
  131. package/templates/claude-global/skills/dwy-frontend-core/SKILL.md +0 -181
  132. package/templates/project/web/.claude/commands/deploy.md +0 -4
  133. package/templates/project/web/backend/src/app/models/base.py +0 -5
  134. package/templates/project/web/deploy.sh.hbs +0 -113
  135. package/templates/project/web/frontend/src/App.vue +0 -3
  136. package/templates/project/web/frontend/src/api/client.ts +0 -13
  137. package/templates/project/web/frontend/src/style.css +0 -1
  138. package/templates/project/web/frontend/src/views/HomeView.vue +0 -5
  139. package/templates/project/web/frontend/tsconfig.app.json +0 -14
  140. package/templates/project/web/frontend/tsconfig.json +0 -4
  141. /package/templates/{project/web/backend → claude-global/skills/dwy-pentest/scripts}/.python-version +0 -0
  142. /package/templates/project/{web/backend → backend}/Dockerfile +0 -0
  143. /package/templates/project/{web/backend → backend}/alembic/env.py.hbs +0 -0
  144. /package/templates/{claude-global/skills → project/backend/alembic/versions}/.gitkeep +0 -0
  145. /package/templates/project/{web → backend}/docker-compose.dev.yml.hbs +0 -0
  146. /package/templates/project/{web/backend → backend}/src/app/__init__.py +0 -0
  147. /package/templates/project/{web/backend → backend}/src/app/models/__init__.py +0 -0
  148. /package/templates/project/{web/backend → backend}/src/app/routers/__init__.py +0 -0
  149. /package/templates/project/{web/backend → backend}/src/app/routers/health.py +0 -0
  150. /package/templates/project/{web/backend → backend}/src/app/schemas/__init__.py +0 -0
  151. /package/templates/project/{web/backend → backend}/src/app/services/__init__.py +0 -0
  152. /package/templates/project/{web/backend → backend}/tests/__init__.py +0 -0
  153. /package/templates/project/{web/backend → backend}/tests/conftest.py.hbs +0 -0
  154. /package/templates/project/{web/backend/alembic/versions → mobile}/.gitkeep +0 -0
  155. /package/templates/project/web/{frontend → apps/web}/src/components/.gitkeep +0 -0
  156. /package/templates/project/web/{frontend → apps/web}/src/main.ts +0 -0
@@ -0,0 +1,96 @@
1
+ """CVSS 评分映射 + 严重性分级工具。"""
2
+
3
+ from dataclasses import dataclass
4
+ from enum import Enum
5
+
6
+
7
+ class Severity(str, Enum):
8
+ CRITICAL = "Critical"
9
+ HIGH = "High"
10
+ MEDIUM = "Medium"
11
+ LOW = "Low"
12
+ INFO = "Info"
13
+
14
+
15
+ @dataclass
16
+ class Finding:
17
+ """统一的漏洞发现记录。"""
18
+
19
+ title: str
20
+ severity: Severity
21
+ cvss: float
22
+ cve_id: str = ""
23
+ description: str = ""
24
+ evidence: str = ""
25
+ affected_service: str = ""
26
+ discovery_tool: str = ""
27
+ remediation: str = ""
28
+ references: list[str] | None = None
29
+
30
+ def to_dict(self) -> dict:
31
+ return {
32
+ "title": self.title,
33
+ "severity": self.severity.value,
34
+ "cvss": self.cvss,
35
+ "cve_id": self.cve_id,
36
+ "description": self.description,
37
+ "evidence": self.evidence,
38
+ "affected_service": self.affected_service,
39
+ "discovery_tool": self.discovery_tool,
40
+ "remediation": self.remediation,
41
+ "references": self.references or [],
42
+ }
43
+
44
+
45
+ def cvss_to_severity(score: float) -> Severity:
46
+ """CVSS 3.1 评分 → 严重性等级。"""
47
+ if score >= 9.0:
48
+ return Severity.CRITICAL
49
+ if score >= 7.0:
50
+ return Severity.HIGH
51
+ if score >= 4.0:
52
+ return Severity.MEDIUM
53
+ if score >= 0.1:
54
+ return Severity.LOW
55
+ return Severity.INFO
56
+
57
+
58
+ def nuclei_severity_to_cvss(severity: str) -> float:
59
+ """Nuclei 严重性字符串 → 估算 CVSS 分数。"""
60
+ mapping = {
61
+ "critical": 9.5,
62
+ "high": 7.5,
63
+ "medium": 5.5,
64
+ "low": 2.5,
65
+ "info": 0.0,
66
+ }
67
+ return mapping.get(severity.lower(), 0.0)
68
+
69
+
70
+ def nmap_vuln_to_finding(script_id: str, output: str) -> Finding:
71
+ """将 nmap NSE 漏洞脚本输出转为 Finding。"""
72
+ # 常见 NSE 漏洞脚本的严重性映射
73
+ critical_scripts = {"ssl-heartbleed", "smb-vuln-ms17-010", "http-shellshock"}
74
+ high_scripts = {"smb-vuln-ms08-067", "ssl-poodle", "http-vuln-cve2017-5638"}
75
+ medium_scripts = {"ssl-dh-params", "http-csrf", "http-sql-injection"}
76
+
77
+ if script_id in critical_scripts:
78
+ severity = Severity.CRITICAL
79
+ cvss = 9.5
80
+ elif script_id in high_scripts:
81
+ severity = Severity.HIGH
82
+ cvss = 7.5
83
+ elif script_id in medium_scripts:
84
+ severity = Severity.MEDIUM
85
+ cvss = 5.5
86
+ else:
87
+ severity = Severity.MEDIUM
88
+ cvss = 5.0
89
+
90
+ return Finding(
91
+ title=script_id.replace("-", " ").replace("_", " ").title(),
92
+ severity=severity,
93
+ cvss=cvss,
94
+ description=output[:500],
95
+ discovery_tool="nmap-nse",
96
+ )
@@ -0,0 +1,113 @@
1
+ """目标验证:IP/域名格式校验、私网地址检测。"""
2
+
3
+ import ipaddress
4
+ import re
5
+ from dataclasses import dataclass
6
+ from urllib.parse import urlparse
7
+
8
+
9
+ @dataclass
10
+ class Target:
11
+ """解析后的目标信息。"""
12
+
13
+ raw: str
14
+ host: str # 纯 IP 或域名
15
+ port: int | None = None
16
+ scheme: str | None = None # http/https
17
+ path: str = "/"
18
+ is_ip: bool = False
19
+ is_private: bool = False
20
+
21
+ @property
22
+ def base_url(self) -> str:
23
+ if self.scheme:
24
+ port_part = f":{self.port}" if self.port and self.port not in (80, 443) else ""
25
+ return f"{self.scheme}://{self.host}{port_part}"
26
+ return self.host
27
+
28
+ @property
29
+ def web_url(self) -> str | None:
30
+ """如果有 scheme 返回完整 URL,否则 None。"""
31
+ if self.scheme:
32
+ port_part = f":{self.port}" if self.port and self.port not in (80, 443) else ""
33
+ return f"{self.scheme}://{self.host}{port_part}{self.path}"
34
+ return None
35
+
36
+
37
+ _DOMAIN_RE = re.compile(
38
+ r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.[A-Za-z0-9-]{1,63})*\.[A-Za-z]{2,}$"
39
+ )
40
+
41
+
42
+ def parse_target(raw: str) -> Target:
43
+ """解析用户输入的目标字符串。
44
+
45
+ 支持格式:
46
+ - IP: 192.168.1.1, 10.0.0.1
47
+ - 域名: example.com, sub.example.com
48
+ - URL: http://example.com, https://example.com:8443/path
49
+ - IP:端口: 192.168.1.1:8080
50
+ """
51
+ raw = raw.strip()
52
+ scheme = None
53
+ host = raw
54
+ port = None
55
+ path = "/"
56
+
57
+ # 尝试解析为 URL
58
+ if "://" in raw:
59
+ parsed = urlparse(raw)
60
+ scheme = parsed.scheme or None
61
+ host = parsed.hostname or raw
62
+ port = parsed.port
63
+ path = parsed.path or "/"
64
+ elif ":" in raw and not raw.startswith("["):
65
+ # IP:port 或 host:port
66
+ parts = raw.rsplit(":", 1)
67
+ if parts[1].isdigit():
68
+ host = parts[0]
69
+ port = int(parts[1])
70
+
71
+ # 判断是否为 IP
72
+ is_ip = False
73
+ is_private = False
74
+ try:
75
+ addr = ipaddress.ip_address(host)
76
+ is_ip = True
77
+ is_private = addr.is_private or addr.is_loopback or addr.is_reserved
78
+ except ValueError:
79
+ pass
80
+
81
+ return Target(
82
+ raw=raw,
83
+ host=host,
84
+ port=port,
85
+ scheme=scheme,
86
+ path=path,
87
+ is_ip=is_ip,
88
+ is_private=is_private,
89
+ )
90
+
91
+
92
+ def validate_target(raw: str) -> tuple[Target | None, str]:
93
+ """校验目标合法性。
94
+
95
+ Returns:
96
+ (target, warning) — target=None 表示无效,warning 为警告信息(私网等)。
97
+ """
98
+ target = parse_target(raw)
99
+
100
+ # 校验 host 格式
101
+ if not target.is_ip and not _DOMAIN_RE.match(target.host):
102
+ return None, f"无效的目标地址: '{target.host}'(不是有效的 IP 或域名)"
103
+
104
+ # 端口范围
105
+ if target.port is not None and not (1 <= target.port <= 65535):
106
+ return None, f"无效的端口号: {target.port}"
107
+
108
+ # 私网地址警告
109
+ warning = ""
110
+ if target.is_private:
111
+ warning = f"目标 {target.host} 是私网/本地地址,请确认这是你的测试环境"
112
+
113
+ return target, warning
@@ -0,0 +1,289 @@
1
+ """阶段 6:认证与凭据测试 — 弱密码检测、默认凭据探测(多服务并发)。
2
+
3
+ 用法:
4
+ uv run pentest_auth.py --target <host> --output-dir <dir> [--services ssh,ftp,http]
5
+ """
6
+
7
+ import argparse
8
+ import asyncio
9
+ import sys
10
+ import time
11
+ from concurrent.futures import ThreadPoolExecutor
12
+ from pathlib import Path
13
+
14
+ import httpx
15
+ from rich.console import Console
16
+ from rich.table import Table
17
+
18
+ from lib.config import DEFAULT_CREDENTIALS, MAX_AUTH_ATTEMPTS, WEAK_PASSWORDS
19
+ from lib.executor import AuditLog, run_command, save_json_result
20
+ from lib.validator import validate_target
21
+
22
+ console = Console()
23
+
24
+ LOGIN_PATHS = ["/login", "/admin", "/admin/login", "/user/login", "/auth/login", "/wp-login.php"]
25
+ LOGIN_KEYWORDS = ["password", "passwd", "login", "sign in"]
26
+
27
+
28
+ def test_ssh(host: str, port: int, audit: AuditLog) -> list[dict]:
29
+ """SSH 弱密码检测。"""
30
+ findings = []
31
+ console.print(f"[bold]SSH 弱密码测试: {host}:{port}[/bold]")
32
+
33
+ usernames = ["root", "admin", "ubuntu", "ec2-user"]
34
+ max_passwords = MAX_AUTH_ATTEMPTS // len(usernames)
35
+ passwords = WEAK_PASSWORDS[:max_passwords]
36
+
37
+ pass_file = Path("/tmp/dwy_pentest_passwords.txt")
38
+ pass_file.write_text("\n".join(passwords))
39
+ user_file = Path("/tmp/dwy_pentest_users.txt")
40
+ user_file.write_text("\n".join(usernames))
41
+
42
+ cmd = [
43
+ "hydra",
44
+ "-L", str(user_file),
45
+ "-P", str(pass_file),
46
+ "-t", "4",
47
+ "-f",
48
+ "-o", "-",
49
+ "-s", str(port),
50
+ host,
51
+ "ssh",
52
+ ]
53
+
54
+ result = run_command(cmd, timeout=120, audit=audit)
55
+
56
+ pass_file.unlink(missing_ok=True)
57
+ user_file.unlink(missing_ok=True)
58
+
59
+ if result.returncode == -3:
60
+ console.print("[yellow]hydra 未安装,跳过 SSH 弱密码测试[/yellow]")
61
+ return findings
62
+
63
+ if result.blocked:
64
+ console.print(f"[red]hydra 被安全策略拦截: {result.block_reason}[/red]")
65
+ return findings
66
+
67
+ for line in result.stdout.split("\n"):
68
+ if "host:" in line.lower() and ("login:" in line.lower() or "password:" in line.lower()):
69
+ findings.append({
70
+ "type": "Weak SSH Credential",
71
+ "detail": line.strip(),
72
+ "service": f"ssh:{port}",
73
+ "severity": "Critical",
74
+ "cvss": 9.8,
75
+ })
76
+ console.print(f" [red bold][CRITICAL] 发现弱密码! {line.strip()}[/red bold]")
77
+
78
+ if not findings:
79
+ console.print(f" [green]SSH 弱密码测试通过(测试了 {len(usernames) * len(passwords)} 组凭据)[/green]")
80
+
81
+ return findings
82
+
83
+
84
+ def test_ftp(host: str, port: int, audit: AuditLog) -> list[dict]:
85
+ """FTP 匿名登录 + 弱密码检测(匿名探测与 hydra 弱密码并发)。"""
86
+ findings = []
87
+ console.print(f"[bold]FTP 测试: {host}:{port}[/bold]")
88
+
89
+ creds = DEFAULT_CREDENTIALS[:10]
90
+ user_file = Path("/tmp/dwy_pentest_ftp_users.txt")
91
+ pass_file = Path("/tmp/dwy_pentest_ftp_pass.txt")
92
+ user_file.write_text("\n".join(set(u for u, _ in creds)))
93
+ pass_file.write_text("\n".join(set(p for _, p in creds)))
94
+
95
+ anon_cmd = ["nmap", "-p", str(port), "--script", "ftp-anon", host]
96
+ hydra_cmd = [
97
+ "hydra",
98
+ "-L", str(user_file),
99
+ "-P", str(pass_file),
100
+ "-t", "4",
101
+ "-f",
102
+ "-s", str(port),
103
+ host,
104
+ "ftp",
105
+ ]
106
+
107
+ # 匿名探测 + hydra 弱密码 并发
108
+ with ThreadPoolExecutor(max_workers=2) as pool:
109
+ anon_future = pool.submit(run_command, anon_cmd, 30, audit)
110
+ hydra_future = pool.submit(run_command, hydra_cmd, 60, audit)
111
+ anon_result = anon_future.result()
112
+ hydra_result = hydra_future.result()
113
+
114
+ user_file.unlink(missing_ok=True)
115
+ pass_file.unlink(missing_ok=True)
116
+
117
+ if "Anonymous FTP login allowed" in anon_result.stdout:
118
+ findings.append({
119
+ "type": "FTP Anonymous Login",
120
+ "detail": "FTP 服务允许匿名登录",
121
+ "service": f"ftp:{port}",
122
+ "severity": "High",
123
+ "cvss": 7.5,
124
+ })
125
+ console.print(" [red][HIGH] FTP 匿名登录已开启![/red]")
126
+
127
+ for line in hydra_result.stdout.split("\n"):
128
+ if "host:" in line.lower() and "login:" in line.lower():
129
+ findings.append({
130
+ "type": "Weak FTP Credential",
131
+ "detail": line.strip(),
132
+ "service": f"ftp:{port}",
133
+ "severity": "Critical",
134
+ "cvss": 9.8,
135
+ })
136
+ console.print(f" [red bold][CRITICAL] {line.strip()}[/red bold]")
137
+
138
+ if not findings:
139
+ console.print(" [green]FTP 安全测试通过[/green]")
140
+
141
+ return findings
142
+
143
+
144
+ async def _async_probe_login_path(client: httpx.AsyncClient, url: str, path: str) -> str | None:
145
+ try:
146
+ resp = await client.get(f"{url}{path}")
147
+ if resp.status_code == 200 and any(kw in resp.text.lower() for kw in LOGIN_KEYWORDS):
148
+ return f"{url}{path}"
149
+ except Exception:
150
+ return None
151
+ return None
152
+
153
+
154
+ async def _async_try_credential(
155
+ client: httpx.AsyncClient, login_url: str, username: str, password: str
156
+ ) -> dict | None:
157
+ try:
158
+ resp = await client.post(
159
+ login_url,
160
+ data={"username": username, "password": password},
161
+ )
162
+ if resp.status_code in (302, 303) and "login" not in resp.headers.get("location", "").lower():
163
+ return {
164
+ "type": "Default HTTP Credential",
165
+ "detail": f"登录成功: {username}:{password}",
166
+ "service": login_url,
167
+ "severity": "Critical",
168
+ "cvss": 9.8,
169
+ }
170
+ except Exception:
171
+ return None
172
+ return None
173
+
174
+
175
+ async def _async_test_http_login(url: str) -> tuple[list[dict], int]:
176
+ findings: list[dict] = []
177
+ tested = 0
178
+
179
+ # 并发探测所有可能的登录页面
180
+ async with httpx.AsyncClient(timeout=10, verify=False, follow_redirects=True) as client:
181
+ path_tasks = [_async_probe_login_path(client, url, p) for p in LOGIN_PATHS]
182
+ path_results = await asyncio.gather(*path_tasks, return_exceptions=True)
183
+
184
+ found_login = next((r for r in path_results if isinstance(r, str)), None)
185
+ if not found_login:
186
+ return findings, tested
187
+
188
+ console.print(f" 发现登录页面: {found_login}")
189
+
190
+ # 并发尝试默认凭据(受 MAX_AUTH_ATTEMPTS 限制)
191
+ creds_to_try = DEFAULT_CREDENTIALS[: min(10, MAX_AUTH_ATTEMPTS)]
192
+ tested = len(creds_to_try)
193
+
194
+ async with httpx.AsyncClient(timeout=10, verify=False, follow_redirects=False) as client:
195
+ cred_tasks = [_async_try_credential(client, found_login, u, p) for u, p in creds_to_try]
196
+ cred_results = await asyncio.gather(*cred_tasks, return_exceptions=True)
197
+
198
+ for r in cred_results:
199
+ if isinstance(r, dict):
200
+ console.print(f" [red bold][CRITICAL] 默认凭据有效: {r['detail']}[/red bold]")
201
+ findings.append(r)
202
+ break
203
+
204
+ return findings, tested
205
+
206
+
207
+ def test_http_login(url: str, audit: AuditLog) -> list[dict]:
208
+ """HTTP 登录表单默认凭据检测(路径探测 + 凭据尝试并发)。"""
209
+ console.print(f"[bold]HTTP 登录测试: {url}[/bold]")
210
+ findings, tested = asyncio.run(_async_test_http_login(url))
211
+
212
+ if not findings and tested == 0:
213
+ console.print(" [green]未发现标准登录页面[/green]")
214
+ elif not findings:
215
+ console.print(f" [green]HTTP 默认凭据测试通过(测试了 {tested} 组)[/green]")
216
+
217
+ return findings
218
+
219
+
220
+ def main():
221
+ parser = argparse.ArgumentParser(description="渗透测试 - 认证测试(多服务并发)")
222
+ parser.add_argument("--target", required=True, help="目标 IP/域名/URL")
223
+ parser.add_argument("--output-dir", required=True, help="输出目录")
224
+ parser.add_argument("--services", default="ssh,http", help="测试服务列表,逗号分隔(ssh,ftp,http)")
225
+ parser.add_argument("--ssh-port", type=int, default=22, help="SSH 端口")
226
+ parser.add_argument("--ftp-port", type=int, default=21, help="FTP 端口")
227
+ args = parser.parse_args()
228
+
229
+ target, warning = validate_target(args.target)
230
+ if target is None:
231
+ console.print(f"[red]{warning}[/red]")
232
+ sys.exit(1)
233
+ if warning:
234
+ console.print(f"[yellow]{warning}[/yellow]")
235
+
236
+ output_dir = Path(args.output_dir)
237
+ output_dir.mkdir(parents=True, exist_ok=True)
238
+ audit = AuditLog(output_dir / "audit.log")
239
+
240
+ services = [s.strip() for s in args.services.split(",")]
241
+
242
+ # 多服务并发测试
243
+ console.print(f"[bold cyan]并发测试服务: {', '.join(services)}[/bold cyan]")
244
+ tasks: dict = {}
245
+ with ThreadPoolExecutor(max_workers=len(services) or 1) as pool:
246
+ if "ssh" in services:
247
+ tasks["ssh"] = pool.submit(test_ssh, target.host, args.ssh_port, audit)
248
+ if "ftp" in services:
249
+ tasks["ftp"] = pool.submit(test_ftp, target.host, args.ftp_port, audit)
250
+ if "http" in services:
251
+ url = target.web_url or f"http://{target.host}"
252
+ tasks["http"] = pool.submit(test_http_login, url, audit)
253
+
254
+ all_findings: list[dict] = []
255
+ for name, fut in tasks.items():
256
+ try:
257
+ all_findings.extend(fut.result())
258
+ except Exception as e:
259
+ console.print(f"[red]{name} 测试异常: {e}[/red]")
260
+
261
+ result = {
262
+ "target": target.host,
263
+ "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
264
+ "services_tested": services,
265
+ "max_attempts_per_service": MAX_AUTH_ATTEMPTS,
266
+ "findings": all_findings,
267
+ "total": len(all_findings),
268
+ }
269
+
270
+ output_file = output_dir / "auth_test.json"
271
+ save_json_result(result, output_file)
272
+ console.print(f"\n[green]结果已保存: {output_file}[/green]")
273
+
274
+ if all_findings:
275
+ table = Table(title="认证测试发现")
276
+ table.add_column("类型")
277
+ table.add_column("严重性")
278
+ table.add_column("详情")
279
+ for f in all_findings:
280
+ table.add_row(f["type"], f["severity"], f["detail"][:60])
281
+ console.print(table)
282
+
283
+ console.print(f"\n[red bold]!! 发现 {len(all_findings)} 个凭据安全问题 !![/red bold]")
284
+ else:
285
+ console.print(f"\n[green]所有认证测试通过[/green]")
286
+
287
+
288
+ if __name__ == "__main__":
289
+ main()