aiptx 2.0.7__py3-none-any.whl
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.
- aipt_v2/__init__.py +110 -0
- aipt_v2/__main__.py +24 -0
- aipt_v2/agents/AIPTxAgent/__init__.py +10 -0
- aipt_v2/agents/AIPTxAgent/aiptx_agent.py +211 -0
- aipt_v2/agents/__init__.py +46 -0
- aipt_v2/agents/base.py +520 -0
- aipt_v2/agents/exploit_agent.py +688 -0
- aipt_v2/agents/ptt.py +406 -0
- aipt_v2/agents/state.py +168 -0
- aipt_v2/app.py +957 -0
- aipt_v2/browser/__init__.py +31 -0
- aipt_v2/browser/automation.py +458 -0
- aipt_v2/browser/crawler.py +453 -0
- aipt_v2/cli.py +2933 -0
- aipt_v2/compliance/__init__.py +71 -0
- aipt_v2/compliance/compliance_report.py +449 -0
- aipt_v2/compliance/framework_mapper.py +424 -0
- aipt_v2/compliance/nist_mapping.py +345 -0
- aipt_v2/compliance/owasp_mapping.py +330 -0
- aipt_v2/compliance/pci_mapping.py +297 -0
- aipt_v2/config.py +341 -0
- aipt_v2/core/__init__.py +43 -0
- aipt_v2/core/agent.py +630 -0
- aipt_v2/core/llm.py +395 -0
- aipt_v2/core/memory.py +305 -0
- aipt_v2/core/ptt.py +329 -0
- aipt_v2/database/__init__.py +14 -0
- aipt_v2/database/models.py +232 -0
- aipt_v2/database/repository.py +384 -0
- aipt_v2/docker/__init__.py +23 -0
- aipt_v2/docker/builder.py +260 -0
- aipt_v2/docker/manager.py +222 -0
- aipt_v2/docker/sandbox.py +371 -0
- aipt_v2/evasion/__init__.py +58 -0
- aipt_v2/evasion/request_obfuscator.py +272 -0
- aipt_v2/evasion/tls_fingerprint.py +285 -0
- aipt_v2/evasion/ua_rotator.py +301 -0
- aipt_v2/evasion/waf_bypass.py +439 -0
- aipt_v2/execution/__init__.py +23 -0
- aipt_v2/execution/executor.py +302 -0
- aipt_v2/execution/parser.py +544 -0
- aipt_v2/execution/terminal.py +337 -0
- aipt_v2/health.py +437 -0
- aipt_v2/intelligence/__init__.py +194 -0
- aipt_v2/intelligence/adaptation.py +474 -0
- aipt_v2/intelligence/auth.py +520 -0
- aipt_v2/intelligence/chaining.py +775 -0
- aipt_v2/intelligence/correlation.py +536 -0
- aipt_v2/intelligence/cve_aipt.py +334 -0
- aipt_v2/intelligence/cve_info.py +1111 -0
- aipt_v2/intelligence/knowledge_graph.py +590 -0
- aipt_v2/intelligence/learning.py +626 -0
- aipt_v2/intelligence/llm_analyzer.py +502 -0
- aipt_v2/intelligence/llm_tool_selector.py +518 -0
- aipt_v2/intelligence/payload_generator.py +562 -0
- aipt_v2/intelligence/rag.py +239 -0
- aipt_v2/intelligence/scope.py +442 -0
- aipt_v2/intelligence/searchers/__init__.py +5 -0
- aipt_v2/intelligence/searchers/exploitdb_searcher.py +523 -0
- aipt_v2/intelligence/searchers/github_searcher.py +467 -0
- aipt_v2/intelligence/searchers/google_searcher.py +281 -0
- aipt_v2/intelligence/tools.json +443 -0
- aipt_v2/intelligence/triage.py +670 -0
- aipt_v2/interactive_shell.py +559 -0
- aipt_v2/interface/__init__.py +5 -0
- aipt_v2/interface/cli.py +230 -0
- aipt_v2/interface/main.py +501 -0
- aipt_v2/interface/tui.py +1276 -0
- aipt_v2/interface/utils.py +583 -0
- aipt_v2/llm/__init__.py +39 -0
- aipt_v2/llm/config.py +26 -0
- aipt_v2/llm/llm.py +514 -0
- aipt_v2/llm/memory.py +214 -0
- aipt_v2/llm/request_queue.py +89 -0
- aipt_v2/llm/utils.py +89 -0
- aipt_v2/local_tool_installer.py +1467 -0
- aipt_v2/models/__init__.py +15 -0
- aipt_v2/models/findings.py +295 -0
- aipt_v2/models/phase_result.py +224 -0
- aipt_v2/models/scan_config.py +207 -0
- aipt_v2/monitoring/grafana/dashboards/aipt-dashboard.json +355 -0
- aipt_v2/monitoring/grafana/dashboards/default.yml +17 -0
- aipt_v2/monitoring/grafana/datasources/prometheus.yml +17 -0
- aipt_v2/monitoring/prometheus.yml +60 -0
- aipt_v2/orchestration/__init__.py +52 -0
- aipt_v2/orchestration/pipeline.py +398 -0
- aipt_v2/orchestration/progress.py +300 -0
- aipt_v2/orchestration/scheduler.py +296 -0
- aipt_v2/orchestrator.py +2427 -0
- aipt_v2/payloads/__init__.py +27 -0
- aipt_v2/payloads/cmdi.py +150 -0
- aipt_v2/payloads/sqli.py +263 -0
- aipt_v2/payloads/ssrf.py +204 -0
- aipt_v2/payloads/templates.py +222 -0
- aipt_v2/payloads/traversal.py +166 -0
- aipt_v2/payloads/xss.py +204 -0
- aipt_v2/prompts/__init__.py +60 -0
- aipt_v2/proxy/__init__.py +29 -0
- aipt_v2/proxy/history.py +352 -0
- aipt_v2/proxy/interceptor.py +452 -0
- aipt_v2/recon/__init__.py +44 -0
- aipt_v2/recon/dns.py +241 -0
- aipt_v2/recon/osint.py +367 -0
- aipt_v2/recon/subdomain.py +372 -0
- aipt_v2/recon/tech_detect.py +311 -0
- aipt_v2/reports/__init__.py +17 -0
- aipt_v2/reports/generator.py +313 -0
- aipt_v2/reports/html_report.py +378 -0
- aipt_v2/runtime/__init__.py +53 -0
- aipt_v2/runtime/base.py +30 -0
- aipt_v2/runtime/docker.py +401 -0
- aipt_v2/runtime/local.py +346 -0
- aipt_v2/runtime/tool_server.py +205 -0
- aipt_v2/runtime/vps.py +830 -0
- aipt_v2/scanners/__init__.py +28 -0
- aipt_v2/scanners/base.py +273 -0
- aipt_v2/scanners/nikto.py +244 -0
- aipt_v2/scanners/nmap.py +402 -0
- aipt_v2/scanners/nuclei.py +273 -0
- aipt_v2/scanners/web.py +454 -0
- aipt_v2/scripts/security_audit.py +366 -0
- aipt_v2/setup_wizard.py +941 -0
- aipt_v2/skills/__init__.py +80 -0
- aipt_v2/skills/agents/__init__.py +14 -0
- aipt_v2/skills/agents/api_tester.py +706 -0
- aipt_v2/skills/agents/base.py +477 -0
- aipt_v2/skills/agents/code_review.py +459 -0
- aipt_v2/skills/agents/security_agent.py +336 -0
- aipt_v2/skills/agents/web_pentest.py +818 -0
- aipt_v2/skills/prompts/__init__.py +647 -0
- aipt_v2/system_detector.py +539 -0
- aipt_v2/telemetry/__init__.py +7 -0
- aipt_v2/telemetry/tracer.py +347 -0
- aipt_v2/terminal/__init__.py +28 -0
- aipt_v2/terminal/executor.py +400 -0
- aipt_v2/terminal/sandbox.py +350 -0
- aipt_v2/tools/__init__.py +44 -0
- aipt_v2/tools/active_directory/__init__.py +78 -0
- aipt_v2/tools/active_directory/ad_config.py +238 -0
- aipt_v2/tools/active_directory/bloodhound_wrapper.py +447 -0
- aipt_v2/tools/active_directory/kerberos_attacks.py +430 -0
- aipt_v2/tools/active_directory/ldap_enum.py +533 -0
- aipt_v2/tools/active_directory/smb_attacks.py +505 -0
- aipt_v2/tools/agents_graph/__init__.py +19 -0
- aipt_v2/tools/agents_graph/agents_graph_actions.py +69 -0
- aipt_v2/tools/api_security/__init__.py +76 -0
- aipt_v2/tools/api_security/api_discovery.py +608 -0
- aipt_v2/tools/api_security/graphql_scanner.py +622 -0
- aipt_v2/tools/api_security/jwt_analyzer.py +577 -0
- aipt_v2/tools/api_security/openapi_fuzzer.py +761 -0
- aipt_v2/tools/browser/__init__.py +5 -0
- aipt_v2/tools/browser/browser_actions.py +238 -0
- aipt_v2/tools/browser/browser_instance.py +535 -0
- aipt_v2/tools/browser/tab_manager.py +344 -0
- aipt_v2/tools/cloud/__init__.py +70 -0
- aipt_v2/tools/cloud/cloud_config.py +273 -0
- aipt_v2/tools/cloud/cloud_scanner.py +639 -0
- aipt_v2/tools/cloud/prowler_tool.py +571 -0
- aipt_v2/tools/cloud/scoutsuite_tool.py +359 -0
- aipt_v2/tools/executor.py +307 -0
- aipt_v2/tools/parser.py +408 -0
- aipt_v2/tools/proxy/__init__.py +5 -0
- aipt_v2/tools/proxy/proxy_actions.py +103 -0
- aipt_v2/tools/proxy/proxy_manager.py +789 -0
- aipt_v2/tools/registry.py +196 -0
- aipt_v2/tools/scanners/__init__.py +343 -0
- aipt_v2/tools/scanners/acunetix_tool.py +712 -0
- aipt_v2/tools/scanners/burp_tool.py +631 -0
- aipt_v2/tools/scanners/config.py +156 -0
- aipt_v2/tools/scanners/nessus_tool.py +588 -0
- aipt_v2/tools/scanners/zap_tool.py +612 -0
- aipt_v2/tools/terminal/__init__.py +5 -0
- aipt_v2/tools/terminal/terminal_actions.py +37 -0
- aipt_v2/tools/terminal/terminal_manager.py +153 -0
- aipt_v2/tools/terminal/terminal_session.py +449 -0
- aipt_v2/tools/tool_processing.py +108 -0
- aipt_v2/utils/__init__.py +17 -0
- aipt_v2/utils/logging.py +202 -0
- aipt_v2/utils/model_manager.py +187 -0
- aipt_v2/utils/searchers/__init__.py +269 -0
- aipt_v2/verify_install.py +793 -0
- aiptx-2.0.7.dist-info/METADATA +345 -0
- aiptx-2.0.7.dist-info/RECORD +187 -0
- aiptx-2.0.7.dist-info/WHEEL +5 -0
- aiptx-2.0.7.dist-info/entry_points.txt +7 -0
- aiptx-2.0.7.dist-info/licenses/LICENSE +21 -0
- aiptx-2.0.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT Subdomain Enumeration
|
|
3
|
+
|
|
4
|
+
Subdomain discovery using multiple sources and tools.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import logging
|
|
10
|
+
import re
|
|
11
|
+
import socket
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from typing import Optional, Set
|
|
15
|
+
|
|
16
|
+
import httpx
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class Subdomain:
|
|
23
|
+
"""Discovered subdomain"""
|
|
24
|
+
domain: str
|
|
25
|
+
ip: str = ""
|
|
26
|
+
status: str = "unknown" # alive, dead, unknown
|
|
27
|
+
http_status: int = 0
|
|
28
|
+
https_status: int = 0
|
|
29
|
+
title: str = ""
|
|
30
|
+
source: str = ""
|
|
31
|
+
discovered_at: datetime = field(default_factory=datetime.utcnow)
|
|
32
|
+
|
|
33
|
+
def to_dict(self) -> dict:
|
|
34
|
+
return {
|
|
35
|
+
"domain": self.domain,
|
|
36
|
+
"ip": self.ip,
|
|
37
|
+
"status": self.status,
|
|
38
|
+
"http_status": self.http_status,
|
|
39
|
+
"https_status": self.https_status,
|
|
40
|
+
"title": self.title,
|
|
41
|
+
"source": self.source,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class SubdomainConfig:
|
|
47
|
+
"""Subdomain enumeration configuration"""
|
|
48
|
+
# Tools to use
|
|
49
|
+
use_subfinder: bool = True
|
|
50
|
+
use_amass: bool = False
|
|
51
|
+
use_crtsh: bool = True
|
|
52
|
+
use_dns_bruteforce: bool = False
|
|
53
|
+
|
|
54
|
+
# Verification
|
|
55
|
+
resolve_dns: bool = True
|
|
56
|
+
check_http: bool = True
|
|
57
|
+
timeout: float = 5.0
|
|
58
|
+
concurrent_requests: int = 50
|
|
59
|
+
|
|
60
|
+
# Wordlist for bruteforce
|
|
61
|
+
wordlist: list[str] = field(default_factory=lambda: [
|
|
62
|
+
"www", "mail", "ftp", "localhost", "webmail", "smtp", "pop", "ns1", "ns2",
|
|
63
|
+
"dns", "dns1", "dns2", "vpn", "gateway", "router", "admin", "administrator",
|
|
64
|
+
"api", "app", "apps", "dev", "development", "staging", "test", "testing",
|
|
65
|
+
"prod", "production", "web", "portal", "secure", "ssl", "cdn", "static",
|
|
66
|
+
"assets", "img", "images", "media", "files", "download", "downloads",
|
|
67
|
+
"blog", "forum", "shop", "store", "support", "help", "docs", "wiki",
|
|
68
|
+
"git", "svn", "repo", "repository", "jenkins", "ci", "build",
|
|
69
|
+
"monitor", "status", "health", "metrics", "grafana", "kibana",
|
|
70
|
+
"db", "database", "mysql", "postgres", "redis", "elastic", "elasticsearch",
|
|
71
|
+
"auth", "login", "sso", "oauth", "identity",
|
|
72
|
+
])
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class SubdomainResult:
|
|
77
|
+
"""Subdomain enumeration results"""
|
|
78
|
+
target: str
|
|
79
|
+
subdomains: list[Subdomain] = field(default_factory=list)
|
|
80
|
+
alive_count: int = 0
|
|
81
|
+
total_found: int = 0
|
|
82
|
+
sources_used: list[str] = field(default_factory=list)
|
|
83
|
+
start_time: Optional[datetime] = None
|
|
84
|
+
end_time: Optional[datetime] = None
|
|
85
|
+
duration_seconds: float = 0.0
|
|
86
|
+
|
|
87
|
+
def get_alive(self) -> list[Subdomain]:
|
|
88
|
+
"""Get only alive subdomains"""
|
|
89
|
+
return [s for s in self.subdomains if s.status == "alive"]
|
|
90
|
+
|
|
91
|
+
def get_by_source(self, source: str) -> list[Subdomain]:
|
|
92
|
+
"""Get subdomains from specific source"""
|
|
93
|
+
return [s for s in self.subdomains if s.source == source]
|
|
94
|
+
|
|
95
|
+
def to_dict(self) -> dict:
|
|
96
|
+
return {
|
|
97
|
+
"target": self.target,
|
|
98
|
+
"total_found": self.total_found,
|
|
99
|
+
"alive_count": self.alive_count,
|
|
100
|
+
"sources_used": self.sources_used,
|
|
101
|
+
"duration_seconds": self.duration_seconds,
|
|
102
|
+
"subdomains": [s.to_dict() for s in self.subdomains],
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class SubdomainEnumerator:
|
|
107
|
+
"""
|
|
108
|
+
Subdomain enumeration from multiple sources.
|
|
109
|
+
|
|
110
|
+
Sources:
|
|
111
|
+
- Subfinder (if installed)
|
|
112
|
+
- Amass (if installed)
|
|
113
|
+
- crt.sh (Certificate Transparency)
|
|
114
|
+
- DNS bruteforce (optional)
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
enumerator = SubdomainEnumerator(SubdomainConfig(
|
|
118
|
+
use_crtsh=True,
|
|
119
|
+
check_http=True,
|
|
120
|
+
))
|
|
121
|
+
result = await enumerator.enumerate("example.com")
|
|
122
|
+
|
|
123
|
+
for subdomain in result.get_alive():
|
|
124
|
+
print(f"{subdomain.domain} -> {subdomain.ip}")
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
def __init__(self, config: Optional[SubdomainConfig] = None):
|
|
128
|
+
self.config = config or SubdomainConfig()
|
|
129
|
+
self._found: Set[str] = set()
|
|
130
|
+
|
|
131
|
+
async def enumerate(self, domain: str) -> SubdomainResult:
|
|
132
|
+
"""
|
|
133
|
+
Enumerate subdomains for a domain.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
domain: Target domain (e.g., example.com)
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
SubdomainResult with discovered subdomains
|
|
140
|
+
"""
|
|
141
|
+
result = SubdomainResult(target=domain)
|
|
142
|
+
result.start_time = datetime.utcnow()
|
|
143
|
+
self._found.clear()
|
|
144
|
+
|
|
145
|
+
# Collect from all sources
|
|
146
|
+
tasks = []
|
|
147
|
+
|
|
148
|
+
if self.config.use_crtsh:
|
|
149
|
+
tasks.append(self._from_crtsh(domain))
|
|
150
|
+
result.sources_used.append("crt.sh")
|
|
151
|
+
|
|
152
|
+
if self.config.use_subfinder and self._tool_available("subfinder"):
|
|
153
|
+
tasks.append(self._from_subfinder(domain))
|
|
154
|
+
result.sources_used.append("subfinder")
|
|
155
|
+
|
|
156
|
+
if self.config.use_amass and self._tool_available("amass"):
|
|
157
|
+
tasks.append(self._from_amass(domain))
|
|
158
|
+
result.sources_used.append("amass")
|
|
159
|
+
|
|
160
|
+
if self.config.use_dns_bruteforce:
|
|
161
|
+
tasks.append(self._dns_bruteforce(domain))
|
|
162
|
+
result.sources_used.append("dns_bruteforce")
|
|
163
|
+
|
|
164
|
+
# Gather results
|
|
165
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
166
|
+
|
|
167
|
+
for source_result in results:
|
|
168
|
+
if isinstance(source_result, list):
|
|
169
|
+
for subdomain in source_result:
|
|
170
|
+
if subdomain.domain not in self._found:
|
|
171
|
+
self._found.add(subdomain.domain)
|
|
172
|
+
result.subdomains.append(subdomain)
|
|
173
|
+
|
|
174
|
+
result.total_found = len(result.subdomains)
|
|
175
|
+
|
|
176
|
+
# Verify subdomains
|
|
177
|
+
if self.config.resolve_dns or self.config.check_http:
|
|
178
|
+
await self._verify_subdomains(result.subdomains)
|
|
179
|
+
|
|
180
|
+
result.alive_count = len([s for s in result.subdomains if s.status == "alive"])
|
|
181
|
+
result.end_time = datetime.utcnow()
|
|
182
|
+
result.duration_seconds = (result.end_time - result.start_time).total_seconds()
|
|
183
|
+
|
|
184
|
+
logger.info(
|
|
185
|
+
f"Enumeration complete: {result.total_found} found, {result.alive_count} alive"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
return result
|
|
189
|
+
|
|
190
|
+
async def _from_crtsh(self, domain: str) -> list[Subdomain]:
|
|
191
|
+
"""Query crt.sh certificate transparency logs"""
|
|
192
|
+
subdomains = []
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
196
|
+
response = await client.get(
|
|
197
|
+
f"https://crt.sh/?q=%.{domain}&output=json"
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if response.status_code == 200:
|
|
201
|
+
data = response.json()
|
|
202
|
+
|
|
203
|
+
for entry in data:
|
|
204
|
+
name = entry.get("name_value", "")
|
|
205
|
+
# Split by newlines (can have multiple names)
|
|
206
|
+
for sub in name.split("\n"):
|
|
207
|
+
sub = sub.strip().lower()
|
|
208
|
+
# Clean wildcards
|
|
209
|
+
sub = sub.replace("*.", "")
|
|
210
|
+
if sub and sub.endswith(domain) and sub not in self._found:
|
|
211
|
+
subdomains.append(Subdomain(
|
|
212
|
+
domain=sub,
|
|
213
|
+
source="crt.sh",
|
|
214
|
+
))
|
|
215
|
+
|
|
216
|
+
except Exception as e:
|
|
217
|
+
logger.debug(f"crt.sh error: {e}")
|
|
218
|
+
|
|
219
|
+
return subdomains
|
|
220
|
+
|
|
221
|
+
async def _from_subfinder(self, domain: str) -> list[Subdomain]:
|
|
222
|
+
"""Run subfinder"""
|
|
223
|
+
subdomains = []
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
process = await asyncio.create_subprocess_exec(
|
|
227
|
+
"subfinder", "-d", domain, "-silent",
|
|
228
|
+
stdout=asyncio.subprocess.PIPE,
|
|
229
|
+
stderr=asyncio.subprocess.PIPE,
|
|
230
|
+
)
|
|
231
|
+
stdout, _ = await asyncio.wait_for(process.communicate(), timeout=120.0)
|
|
232
|
+
|
|
233
|
+
for line in stdout.decode().strip().split("\n"):
|
|
234
|
+
sub = line.strip().lower()
|
|
235
|
+
if sub and sub not in self._found:
|
|
236
|
+
subdomains.append(Subdomain(
|
|
237
|
+
domain=sub,
|
|
238
|
+
source="subfinder",
|
|
239
|
+
))
|
|
240
|
+
|
|
241
|
+
except Exception as e:
|
|
242
|
+
logger.debug(f"subfinder error: {e}")
|
|
243
|
+
|
|
244
|
+
return subdomains
|
|
245
|
+
|
|
246
|
+
async def _from_amass(self, domain: str) -> list[Subdomain]:
|
|
247
|
+
"""Run amass"""
|
|
248
|
+
subdomains = []
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
process = await asyncio.create_subprocess_exec(
|
|
252
|
+
"amass", "enum", "-passive", "-d", domain,
|
|
253
|
+
stdout=asyncio.subprocess.PIPE,
|
|
254
|
+
stderr=asyncio.subprocess.PIPE,
|
|
255
|
+
)
|
|
256
|
+
stdout, _ = await asyncio.wait_for(process.communicate(), timeout=300.0)
|
|
257
|
+
|
|
258
|
+
for line in stdout.decode().strip().split("\n"):
|
|
259
|
+
sub = line.strip().lower()
|
|
260
|
+
if sub and sub not in self._found:
|
|
261
|
+
subdomains.append(Subdomain(
|
|
262
|
+
domain=sub,
|
|
263
|
+
source="amass",
|
|
264
|
+
))
|
|
265
|
+
|
|
266
|
+
except Exception as e:
|
|
267
|
+
logger.debug(f"amass error: {e}")
|
|
268
|
+
|
|
269
|
+
return subdomains
|
|
270
|
+
|
|
271
|
+
async def _dns_bruteforce(self, domain: str) -> list[Subdomain]:
|
|
272
|
+
"""Bruteforce subdomains using wordlist"""
|
|
273
|
+
subdomains = []
|
|
274
|
+
semaphore = asyncio.Semaphore(self.config.concurrent_requests)
|
|
275
|
+
|
|
276
|
+
async def check_subdomain(word: str) -> Optional[Subdomain]:
|
|
277
|
+
async with semaphore:
|
|
278
|
+
subdomain = f"{word}.{domain}"
|
|
279
|
+
try:
|
|
280
|
+
socket.setdefaulttimeout(self.config.timeout)
|
|
281
|
+
ip = socket.gethostbyname(subdomain)
|
|
282
|
+
return Subdomain(
|
|
283
|
+
domain=subdomain,
|
|
284
|
+
ip=ip,
|
|
285
|
+
status="alive",
|
|
286
|
+
source="dns_bruteforce",
|
|
287
|
+
)
|
|
288
|
+
except socket.gaierror:
|
|
289
|
+
return None
|
|
290
|
+
|
|
291
|
+
tasks = [check_subdomain(word) for word in self.config.wordlist]
|
|
292
|
+
results = await asyncio.gather(*tasks)
|
|
293
|
+
|
|
294
|
+
for result in results:
|
|
295
|
+
if result and result.domain not in self._found:
|
|
296
|
+
subdomains.append(result)
|
|
297
|
+
|
|
298
|
+
return subdomains
|
|
299
|
+
|
|
300
|
+
async def _verify_subdomains(self, subdomains: list[Subdomain]) -> None:
|
|
301
|
+
"""Verify subdomains are alive"""
|
|
302
|
+
semaphore = asyncio.Semaphore(self.config.concurrent_requests)
|
|
303
|
+
|
|
304
|
+
async def verify(subdomain: Subdomain) -> None:
|
|
305
|
+
async with semaphore:
|
|
306
|
+
# DNS resolution
|
|
307
|
+
if self.config.resolve_dns and not subdomain.ip:
|
|
308
|
+
try:
|
|
309
|
+
subdomain.ip = socket.gethostbyname(subdomain.domain)
|
|
310
|
+
except socket.gaierror:
|
|
311
|
+
subdomain.status = "dead"
|
|
312
|
+
return
|
|
313
|
+
|
|
314
|
+
# HTTP check
|
|
315
|
+
if self.config.check_http:
|
|
316
|
+
try:
|
|
317
|
+
async with httpx.AsyncClient(
|
|
318
|
+
timeout=self.config.timeout,
|
|
319
|
+
follow_redirects=True,
|
|
320
|
+
verify=False,
|
|
321
|
+
) as client:
|
|
322
|
+
# Try HTTPS first
|
|
323
|
+
try:
|
|
324
|
+
response = await client.get(f"https://{subdomain.domain}")
|
|
325
|
+
subdomain.https_status = response.status_code
|
|
326
|
+
subdomain.status = "alive"
|
|
327
|
+
|
|
328
|
+
# Extract title
|
|
329
|
+
title_match = re.search(
|
|
330
|
+
r"<title[^>]*>([^<]+)</title>",
|
|
331
|
+
response.text,
|
|
332
|
+
re.IGNORECASE,
|
|
333
|
+
)
|
|
334
|
+
if title_match:
|
|
335
|
+
subdomain.title = title_match.group(1).strip()[:100]
|
|
336
|
+
|
|
337
|
+
except Exception:
|
|
338
|
+
# Try HTTP
|
|
339
|
+
try:
|
|
340
|
+
response = await client.get(f"http://{subdomain.domain}")
|
|
341
|
+
subdomain.http_status = response.status_code
|
|
342
|
+
subdomain.status = "alive"
|
|
343
|
+
except Exception:
|
|
344
|
+
pass
|
|
345
|
+
|
|
346
|
+
except Exception:
|
|
347
|
+
pass
|
|
348
|
+
|
|
349
|
+
# Mark as alive if we got an IP
|
|
350
|
+
if subdomain.ip and subdomain.status == "unknown":
|
|
351
|
+
subdomain.status = "alive"
|
|
352
|
+
|
|
353
|
+
await asyncio.gather(*[verify(s) for s in subdomains])
|
|
354
|
+
|
|
355
|
+
def _tool_available(self, tool: str) -> bool:
|
|
356
|
+
"""Check if tool is available"""
|
|
357
|
+
import shutil
|
|
358
|
+
return shutil.which(tool) is not None
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
# Convenience function
|
|
362
|
+
async def enumerate_subdomains(domain: str, quick: bool = True) -> SubdomainResult:
|
|
363
|
+
"""Quick subdomain enumeration"""
|
|
364
|
+
config = SubdomainConfig(
|
|
365
|
+
use_subfinder=not quick,
|
|
366
|
+
use_amass=False,
|
|
367
|
+
use_crtsh=True,
|
|
368
|
+
use_dns_bruteforce=not quick,
|
|
369
|
+
check_http=True,
|
|
370
|
+
)
|
|
371
|
+
enumerator = SubdomainEnumerator(config)
|
|
372
|
+
return await enumerator.enumerate(domain)
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT Technology Detection
|
|
3
|
+
|
|
4
|
+
Web technology fingerprinting and stack detection.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import re
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class Technology:
|
|
21
|
+
"""Detected technology"""
|
|
22
|
+
name: str
|
|
23
|
+
category: str # frontend, backend, framework, cms, server, etc.
|
|
24
|
+
version: str = ""
|
|
25
|
+
confidence: int = 100 # 0-100
|
|
26
|
+
evidence: str = ""
|
|
27
|
+
|
|
28
|
+
def to_dict(self) -> dict:
|
|
29
|
+
return {
|
|
30
|
+
"name": self.name,
|
|
31
|
+
"category": self.category,
|
|
32
|
+
"version": self.version,
|
|
33
|
+
"confidence": self.confidence,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class TechStack:
|
|
39
|
+
"""Complete technology stack"""
|
|
40
|
+
url: str
|
|
41
|
+
technologies: list[Technology] = field(default_factory=list)
|
|
42
|
+
headers: dict[str, str] = field(default_factory=dict)
|
|
43
|
+
cookies: list[str] = field(default_factory=list)
|
|
44
|
+
detected_at: datetime = field(default_factory=datetime.utcnow)
|
|
45
|
+
|
|
46
|
+
def get_by_category(self, category: str) -> list[Technology]:
|
|
47
|
+
"""Get technologies by category"""
|
|
48
|
+
return [t for t in self.technologies if t.category == category]
|
|
49
|
+
|
|
50
|
+
def has_tech(self, name: str) -> bool:
|
|
51
|
+
"""Check if specific technology is present"""
|
|
52
|
+
return any(t.name.lower() == name.lower() for t in self.technologies)
|
|
53
|
+
|
|
54
|
+
def to_dict(self) -> dict:
|
|
55
|
+
return {
|
|
56
|
+
"url": self.url,
|
|
57
|
+
"technologies": [t.to_dict() for t in self.technologies],
|
|
58
|
+
"categories": list(set(t.category for t in self.technologies)),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class TechDetector:
|
|
63
|
+
"""
|
|
64
|
+
Web technology fingerprinting.
|
|
65
|
+
|
|
66
|
+
Detects:
|
|
67
|
+
- Web servers (nginx, Apache, IIS)
|
|
68
|
+
- Frameworks (React, Vue, Angular, Django, Rails)
|
|
69
|
+
- CMS (WordPress, Drupal, Joomla)
|
|
70
|
+
- JavaScript libraries
|
|
71
|
+
- Security tools (WAF, CDN)
|
|
72
|
+
|
|
73
|
+
Example:
|
|
74
|
+
detector = TechDetector()
|
|
75
|
+
stack = await detector.detect("https://example.com")
|
|
76
|
+
|
|
77
|
+
for tech in stack.technologies:
|
|
78
|
+
print(f"{tech.category}: {tech.name} {tech.version}")
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
# Technology fingerprints
|
|
82
|
+
FINGERPRINTS = {
|
|
83
|
+
# Headers
|
|
84
|
+
"headers": {
|
|
85
|
+
"Server": {
|
|
86
|
+
"nginx": ("nginx", "server"),
|
|
87
|
+
"Apache": ("Apache", "server"),
|
|
88
|
+
"Microsoft-IIS": ("IIS", "server"),
|
|
89
|
+
"cloudflare": ("Cloudflare", "cdn"),
|
|
90
|
+
"AmazonS3": ("Amazon S3", "storage"),
|
|
91
|
+
"Varnish": ("Varnish", "cache"),
|
|
92
|
+
"gunicorn": ("Gunicorn", "server"),
|
|
93
|
+
"uvicorn": ("Uvicorn", "server"),
|
|
94
|
+
},
|
|
95
|
+
"X-Powered-By": {
|
|
96
|
+
"PHP": ("PHP", "language"),
|
|
97
|
+
"ASP.NET": ("ASP.NET", "framework"),
|
|
98
|
+
"Express": ("Express.js", "framework"),
|
|
99
|
+
"Next.js": ("Next.js", "framework"),
|
|
100
|
+
"Nuxt": ("Nuxt.js", "framework"),
|
|
101
|
+
},
|
|
102
|
+
"X-Generator": {
|
|
103
|
+
"WordPress": ("WordPress", "cms"),
|
|
104
|
+
"Drupal": ("Drupal", "cms"),
|
|
105
|
+
"Joomla": ("Joomla", "cms"),
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
# HTML patterns
|
|
109
|
+
"html": [
|
|
110
|
+
# Frameworks
|
|
111
|
+
(r"react", "React", "frontend"),
|
|
112
|
+
(r"ng-app|angular", "Angular", "frontend"),
|
|
113
|
+
(r"vue\.js|v-cloak|v-bind", "Vue.js", "frontend"),
|
|
114
|
+
(r"svelte", "Svelte", "frontend"),
|
|
115
|
+
(r"ember", "Ember.js", "frontend"),
|
|
116
|
+
|
|
117
|
+
# CMS
|
|
118
|
+
(r"wp-content|wp-includes", "WordPress", "cms"),
|
|
119
|
+
(r"drupal\.js|drupal\.settings", "Drupal", "cms"),
|
|
120
|
+
(r"joomla", "Joomla", "cms"),
|
|
121
|
+
(r"shopify", "Shopify", "ecommerce"),
|
|
122
|
+
(r"magento", "Magento", "ecommerce"),
|
|
123
|
+
(r"woocommerce", "WooCommerce", "ecommerce"),
|
|
124
|
+
|
|
125
|
+
# JavaScript libraries
|
|
126
|
+
(r"jquery[\.-]?\d|jquery\.min\.js", "jQuery", "javascript"),
|
|
127
|
+
(r"bootstrap[\.-]?\d|bootstrap\.min", "Bootstrap", "css"),
|
|
128
|
+
(r"tailwind", "Tailwind CSS", "css"),
|
|
129
|
+
(r"lodash", "Lodash", "javascript"),
|
|
130
|
+
(r"moment\.js|moment\.min", "Moment.js", "javascript"),
|
|
131
|
+
(r"axios", "Axios", "javascript"),
|
|
132
|
+
|
|
133
|
+
# Analytics
|
|
134
|
+
(r"google-analytics|gtag|ga\.js", "Google Analytics", "analytics"),
|
|
135
|
+
(r"googletagmanager", "Google Tag Manager", "analytics"),
|
|
136
|
+
(r"facebook.*pixel|fbq\(", "Facebook Pixel", "analytics"),
|
|
137
|
+
(r"hotjar", "Hotjar", "analytics"),
|
|
138
|
+
(r"segment\.io|analytics\.js", "Segment", "analytics"),
|
|
139
|
+
|
|
140
|
+
# Security
|
|
141
|
+
(r"recaptcha", "reCAPTCHA", "security"),
|
|
142
|
+
(r"hcaptcha", "hCaptcha", "security"),
|
|
143
|
+
(r"cloudflare", "Cloudflare", "cdn"),
|
|
144
|
+
(r"akamai", "Akamai", "cdn"),
|
|
145
|
+
(r"fastly", "Fastly", "cdn"),
|
|
146
|
+
|
|
147
|
+
# Other
|
|
148
|
+
(r"webpack", "Webpack", "build"),
|
|
149
|
+
(r"vite", "Vite", "build"),
|
|
150
|
+
(r"graphql", "GraphQL", "api"),
|
|
151
|
+
(r"socket\.io", "Socket.IO", "websocket"),
|
|
152
|
+
],
|
|
153
|
+
# Cookie patterns
|
|
154
|
+
"cookies": {
|
|
155
|
+
"PHPSESSID": ("PHP", "language"),
|
|
156
|
+
"ASP.NET_SessionId": ("ASP.NET", "framework"),
|
|
157
|
+
"JSESSIONID": ("Java", "language"),
|
|
158
|
+
"rack.session": ("Ruby/Rack", "framework"),
|
|
159
|
+
"express.sid": ("Express.js", "framework"),
|
|
160
|
+
"connect.sid": ("Connect.js", "framework"),
|
|
161
|
+
"laravel_session": ("Laravel", "framework"),
|
|
162
|
+
"django": ("Django", "framework"),
|
|
163
|
+
"wordpress": ("WordPress", "cms"),
|
|
164
|
+
"wp-settings": ("WordPress", "cms"),
|
|
165
|
+
"__cf_bm": ("Cloudflare", "cdn"),
|
|
166
|
+
},
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
def __init__(self, timeout: float = 10.0):
|
|
170
|
+
self.timeout = timeout
|
|
171
|
+
|
|
172
|
+
async def detect(self, url: str) -> TechStack:
|
|
173
|
+
"""
|
|
174
|
+
Detect technologies used by a website.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
url: Target URL
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
TechStack with detected technologies
|
|
181
|
+
"""
|
|
182
|
+
stack = TechStack(url=url)
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
async with httpx.AsyncClient(
|
|
186
|
+
timeout=self.timeout,
|
|
187
|
+
follow_redirects=True,
|
|
188
|
+
verify=False,
|
|
189
|
+
) as client:
|
|
190
|
+
response = await client.get(url)
|
|
191
|
+
|
|
192
|
+
# Store headers
|
|
193
|
+
stack.headers = dict(response.headers)
|
|
194
|
+
|
|
195
|
+
# Store cookies
|
|
196
|
+
stack.cookies = [c for c in response.cookies.keys()]
|
|
197
|
+
|
|
198
|
+
# Detect from headers
|
|
199
|
+
self._detect_from_headers(response.headers, stack)
|
|
200
|
+
|
|
201
|
+
# Detect from cookies
|
|
202
|
+
self._detect_from_cookies(response.cookies, stack)
|
|
203
|
+
|
|
204
|
+
# Detect from HTML
|
|
205
|
+
self._detect_from_html(response.text, stack)
|
|
206
|
+
|
|
207
|
+
# Extract versions where possible
|
|
208
|
+
self._extract_versions(response.text, stack)
|
|
209
|
+
|
|
210
|
+
except Exception as e:
|
|
211
|
+
logger.error(f"Tech detection error: {e}")
|
|
212
|
+
|
|
213
|
+
# Deduplicate
|
|
214
|
+
seen = set()
|
|
215
|
+
unique = []
|
|
216
|
+
for tech in stack.technologies:
|
|
217
|
+
key = (tech.name, tech.category)
|
|
218
|
+
if key not in seen:
|
|
219
|
+
seen.add(key)
|
|
220
|
+
unique.append(tech)
|
|
221
|
+
stack.technologies = unique
|
|
222
|
+
|
|
223
|
+
logger.info(f"Detected {len(stack.technologies)} technologies")
|
|
224
|
+
return stack
|
|
225
|
+
|
|
226
|
+
def _detect_from_headers(self, headers: httpx.Headers, stack: TechStack) -> None:
|
|
227
|
+
"""Detect technologies from HTTP headers"""
|
|
228
|
+
for header, patterns in self.FINGERPRINTS["headers"].items():
|
|
229
|
+
value = headers.get(header, "")
|
|
230
|
+
if value:
|
|
231
|
+
for pattern, (name, category) in patterns.items():
|
|
232
|
+
if pattern.lower() in value.lower():
|
|
233
|
+
# Extract version if present
|
|
234
|
+
version = ""
|
|
235
|
+
version_match = re.search(rf"{pattern}[/\s]*([\d.]+)", value, re.I)
|
|
236
|
+
if version_match:
|
|
237
|
+
version = version_match.group(1)
|
|
238
|
+
|
|
239
|
+
stack.technologies.append(Technology(
|
|
240
|
+
name=name,
|
|
241
|
+
category=category,
|
|
242
|
+
version=version,
|
|
243
|
+
confidence=100,
|
|
244
|
+
evidence=f"Header: {header}: {value}",
|
|
245
|
+
))
|
|
246
|
+
|
|
247
|
+
def _detect_from_cookies(self, cookies: httpx.Cookies, stack: TechStack) -> None:
|
|
248
|
+
"""Detect technologies from cookies"""
|
|
249
|
+
for cookie_name in cookies.keys():
|
|
250
|
+
for pattern, (name, category) in self.FINGERPRINTS["cookies"].items():
|
|
251
|
+
if pattern.lower() in cookie_name.lower():
|
|
252
|
+
stack.technologies.append(Technology(
|
|
253
|
+
name=name,
|
|
254
|
+
category=category,
|
|
255
|
+
confidence=90,
|
|
256
|
+
evidence=f"Cookie: {cookie_name}",
|
|
257
|
+
))
|
|
258
|
+
|
|
259
|
+
def _detect_from_html(self, html: str, stack: TechStack) -> None:
|
|
260
|
+
"""Detect technologies from HTML content"""
|
|
261
|
+
html_lower = html.lower()
|
|
262
|
+
|
|
263
|
+
for pattern, name, category in self.FINGERPRINTS["html"]:
|
|
264
|
+
if re.search(pattern, html_lower):
|
|
265
|
+
stack.technologies.append(Technology(
|
|
266
|
+
name=name,
|
|
267
|
+
category=category,
|
|
268
|
+
confidence=80,
|
|
269
|
+
evidence=f"HTML pattern: {pattern}",
|
|
270
|
+
))
|
|
271
|
+
|
|
272
|
+
# Check meta generator
|
|
273
|
+
generator_match = re.search(
|
|
274
|
+
r'<meta[^>]*name=["\']generator["\'][^>]*content=["\']([^"\']+)["\']',
|
|
275
|
+
html,
|
|
276
|
+
re.I,
|
|
277
|
+
)
|
|
278
|
+
if generator_match:
|
|
279
|
+
generator = generator_match.group(1)
|
|
280
|
+
stack.technologies.append(Technology(
|
|
281
|
+
name=generator.split()[0],
|
|
282
|
+
category="cms",
|
|
283
|
+
version=generator.split()[1] if len(generator.split()) > 1 else "",
|
|
284
|
+
confidence=100,
|
|
285
|
+
evidence=f"Meta generator: {generator}",
|
|
286
|
+
))
|
|
287
|
+
|
|
288
|
+
def _extract_versions(self, html: str, stack: TechStack) -> None:
|
|
289
|
+
"""Try to extract version numbers"""
|
|
290
|
+
# Version patterns for common technologies
|
|
291
|
+
version_patterns = {
|
|
292
|
+
"jQuery": r"jquery[.-]?(\d+\.\d+(?:\.\d+)?)",
|
|
293
|
+
"Bootstrap": r"bootstrap[.-]?(\d+\.\d+(?:\.\d+)?)",
|
|
294
|
+
"React": r"react[.-]?(\d+\.\d+(?:\.\d+)?)",
|
|
295
|
+
"Vue.js": r"vue[.-]?(\d+\.\d+(?:\.\d+)?)",
|
|
296
|
+
"Angular": r"angular[.-]?(\d+\.\d+(?:\.\d+)?)",
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
for tech in stack.technologies:
|
|
300
|
+
if not tech.version and tech.name in version_patterns:
|
|
301
|
+
pattern = version_patterns[tech.name]
|
|
302
|
+
match = re.search(pattern, html, re.I)
|
|
303
|
+
if match:
|
|
304
|
+
tech.version = match.group(1)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
# Convenience function
|
|
308
|
+
async def detect_tech(url: str) -> TechStack:
|
|
309
|
+
"""Quick technology detection"""
|
|
310
|
+
detector = TechDetector()
|
|
311
|
+
return await detector.detect(url)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT Report Generation
|
|
3
|
+
|
|
4
|
+
Generates professional pentest reports in multiple formats:
|
|
5
|
+
- HTML (standalone, styled)
|
|
6
|
+
- Markdown (for documentation)
|
|
7
|
+
- JSON (for integration)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .generator import ReportGenerator, ReportConfig
|
|
11
|
+
from .html_report import generate_html_report
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"ReportGenerator",
|
|
15
|
+
"ReportConfig",
|
|
16
|
+
"generate_html_report",
|
|
17
|
+
]
|