aiptx 2.0.2__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.
Potentially problematic release.
This version of aiptx might be problematic. Click here for more details.
- 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 +24 -0
- aipt_v2/agents/base.py +520 -0
- aipt_v2/agents/ptt.py +406 -0
- aipt_v2/agents/state.py +168 -0
- aipt_v2/app.py +960 -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 +321 -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 +288 -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 +85 -0
- aipt_v2/intelligence/auth.py +520 -0
- aipt_v2/intelligence/chaining.py +775 -0
- aipt_v2/intelligence/cve_aipt.py +334 -0
- aipt_v2/intelligence/cve_info.py +1111 -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/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/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 +2284 -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 +44 -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/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/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 +201 -0
- aipt_v2/utils/model_manager.py +187 -0
- aipt_v2/utils/searchers/__init__.py +269 -0
- aiptx-2.0.2.dist-info/METADATA +324 -0
- aiptx-2.0.2.dist-info/RECORD +165 -0
- aiptx-2.0.2.dist-info/WHEEL +5 -0
- aiptx-2.0.2.dist-info/entry_points.txt +7 -0
- aiptx-2.0.2.dist-info/licenses/LICENSE +21 -0
- aiptx-2.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT Proxy Interceptor
|
|
3
|
+
|
|
4
|
+
HTTP/HTTPS traffic interception using mitmproxy.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import tempfile
|
|
13
|
+
import threading
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from typing import Any, Callable, Optional
|
|
17
|
+
from urllib.parse import urlparse
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
# mitmproxy import with fallback
|
|
22
|
+
try:
|
|
23
|
+
from mitmproxy import http, options
|
|
24
|
+
from mitmproxy.tools import dump
|
|
25
|
+
MITMPROXY_AVAILABLE = True
|
|
26
|
+
except ImportError:
|
|
27
|
+
MITMPROXY_AVAILABLE = False
|
|
28
|
+
logger.warning("mitmproxy not installed. Install with: pip install mitmproxy")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class InterceptedRequest:
|
|
33
|
+
"""Captured HTTP request"""
|
|
34
|
+
id: str
|
|
35
|
+
timestamp: datetime
|
|
36
|
+
method: str
|
|
37
|
+
url: str
|
|
38
|
+
host: str
|
|
39
|
+
path: str
|
|
40
|
+
headers: dict[str, str]
|
|
41
|
+
body: bytes = b""
|
|
42
|
+
query_params: dict[str, list[str]] = field(default_factory=dict)
|
|
43
|
+
cookies: dict[str, str] = field(default_factory=dict)
|
|
44
|
+
|
|
45
|
+
# Classification
|
|
46
|
+
content_type: str = ""
|
|
47
|
+
is_json: bool = False
|
|
48
|
+
is_form: bool = False
|
|
49
|
+
is_multipart: bool = False
|
|
50
|
+
|
|
51
|
+
def to_dict(self) -> dict:
|
|
52
|
+
return {
|
|
53
|
+
"id": self.id,
|
|
54
|
+
"timestamp": self.timestamp.isoformat(),
|
|
55
|
+
"method": self.method,
|
|
56
|
+
"url": self.url,
|
|
57
|
+
"host": self.host,
|
|
58
|
+
"path": self.path,
|
|
59
|
+
"headers": self.headers,
|
|
60
|
+
"body_size": len(self.body),
|
|
61
|
+
"content_type": self.content_type,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
def get_body_text(self) -> str:
|
|
65
|
+
"""Get body as text"""
|
|
66
|
+
try:
|
|
67
|
+
return self.body.decode("utf-8")
|
|
68
|
+
except UnicodeDecodeError:
|
|
69
|
+
return f"[Binary: {len(self.body)} bytes]"
|
|
70
|
+
|
|
71
|
+
def get_body_json(self) -> Optional[dict]:
|
|
72
|
+
"""Parse body as JSON"""
|
|
73
|
+
if self.is_json:
|
|
74
|
+
try:
|
|
75
|
+
return json.loads(self.body)
|
|
76
|
+
except json.JSONDecodeError:
|
|
77
|
+
pass
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class InterceptedResponse:
|
|
83
|
+
"""Captured HTTP response"""
|
|
84
|
+
request_id: str
|
|
85
|
+
timestamp: datetime
|
|
86
|
+
status_code: int
|
|
87
|
+
reason: str
|
|
88
|
+
headers: dict[str, str]
|
|
89
|
+
body: bytes = b""
|
|
90
|
+
|
|
91
|
+
# Timing
|
|
92
|
+
response_time_ms: float = 0.0
|
|
93
|
+
|
|
94
|
+
# Classification
|
|
95
|
+
content_type: str = ""
|
|
96
|
+
is_json: bool = False
|
|
97
|
+
is_html: bool = False
|
|
98
|
+
|
|
99
|
+
def to_dict(self) -> dict:
|
|
100
|
+
return {
|
|
101
|
+
"request_id": self.request_id,
|
|
102
|
+
"timestamp": self.timestamp.isoformat(),
|
|
103
|
+
"status_code": self.status_code,
|
|
104
|
+
"reason": self.reason,
|
|
105
|
+
"headers": self.headers,
|
|
106
|
+
"body_size": len(self.body),
|
|
107
|
+
"response_time_ms": self.response_time_ms,
|
|
108
|
+
"content_type": self.content_type,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
def get_body_text(self) -> str:
|
|
112
|
+
"""Get body as text"""
|
|
113
|
+
try:
|
|
114
|
+
return self.body.decode("utf-8")
|
|
115
|
+
except UnicodeDecodeError:
|
|
116
|
+
return f"[Binary: {len(self.body)} bytes]"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass
|
|
120
|
+
class ProxyConfig:
|
|
121
|
+
"""Proxy configuration"""
|
|
122
|
+
listen_host: str = "127.0.0.1"
|
|
123
|
+
listen_port: int = 8080
|
|
124
|
+
|
|
125
|
+
# SSL/TLS
|
|
126
|
+
ssl_insecure: bool = True # Accept invalid certs from upstream
|
|
127
|
+
|
|
128
|
+
# Filtering
|
|
129
|
+
include_hosts: list[str] = field(default_factory=list)
|
|
130
|
+
exclude_hosts: list[str] = field(default_factory=lambda: [
|
|
131
|
+
"*.google.com",
|
|
132
|
+
"*.googleapis.com",
|
|
133
|
+
"*.gstatic.com",
|
|
134
|
+
"*.doubleclick.net",
|
|
135
|
+
"*.google-analytics.com",
|
|
136
|
+
])
|
|
137
|
+
|
|
138
|
+
# Capture settings
|
|
139
|
+
capture_requests: bool = True
|
|
140
|
+
capture_responses: bool = True
|
|
141
|
+
max_body_size: int = 10 * 1024 * 1024 # 10MB
|
|
142
|
+
|
|
143
|
+
# Modification
|
|
144
|
+
inject_headers: dict[str, str] = field(default_factory=dict)
|
|
145
|
+
remove_headers: list[str] = field(default_factory=list)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class ProxyInterceptor:
|
|
149
|
+
"""
|
|
150
|
+
HTTP/HTTPS traffic interceptor.
|
|
151
|
+
|
|
152
|
+
Features:
|
|
153
|
+
- Request/response capture
|
|
154
|
+
- Traffic modification
|
|
155
|
+
- Scope filtering
|
|
156
|
+
- Request/response callbacks
|
|
157
|
+
|
|
158
|
+
Example:
|
|
159
|
+
proxy = ProxyInterceptor(ProxyConfig(listen_port=8080))
|
|
160
|
+
|
|
161
|
+
@proxy.on_request
|
|
162
|
+
def handle_request(request):
|
|
163
|
+
print(f"Request: {request.method} {request.url}")
|
|
164
|
+
|
|
165
|
+
await proxy.start()
|
|
166
|
+
# Configure browser to use proxy at 127.0.0.1:8080
|
|
167
|
+
# ...
|
|
168
|
+
await proxy.stop()
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
def __init__(self, config: Optional[ProxyConfig] = None):
|
|
172
|
+
if not MITMPROXY_AVAILABLE:
|
|
173
|
+
raise ImportError("mitmproxy is required. Install with: pip install mitmproxy")
|
|
174
|
+
|
|
175
|
+
self.config = config or ProxyConfig()
|
|
176
|
+
self._requests: dict[str, InterceptedRequest] = {}
|
|
177
|
+
self._responses: dict[str, InterceptedResponse] = {}
|
|
178
|
+
self._request_callbacks: list[Callable[[InterceptedRequest], Optional[InterceptedRequest]]] = []
|
|
179
|
+
self._response_callbacks: list[Callable[[InterceptedResponse], Optional[InterceptedResponse]]] = []
|
|
180
|
+
self._master = None
|
|
181
|
+
self._thread: Optional[threading.Thread] = None
|
|
182
|
+
self._running = False
|
|
183
|
+
self._request_count = 0
|
|
184
|
+
|
|
185
|
+
def on_request(self, callback: Callable[[InterceptedRequest], Optional[InterceptedRequest]]):
|
|
186
|
+
"""Register request callback (decorator)"""
|
|
187
|
+
self._request_callbacks.append(callback)
|
|
188
|
+
return callback
|
|
189
|
+
|
|
190
|
+
def on_response(self, callback: Callable[[InterceptedResponse], Optional[InterceptedResponse]]):
|
|
191
|
+
"""Register response callback (decorator)"""
|
|
192
|
+
self._response_callbacks.append(callback)
|
|
193
|
+
return callback
|
|
194
|
+
|
|
195
|
+
async def start(self) -> bool:
|
|
196
|
+
"""Start the proxy server"""
|
|
197
|
+
if self._running:
|
|
198
|
+
return True
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
# Create mitmproxy options
|
|
202
|
+
opts = options.Options(
|
|
203
|
+
listen_host=self.config.listen_host,
|
|
204
|
+
listen_port=self.config.listen_port,
|
|
205
|
+
ssl_insecure=self.config.ssl_insecure,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Create master
|
|
209
|
+
self._master = dump.DumpMaster(
|
|
210
|
+
opts,
|
|
211
|
+
with_termlog=False,
|
|
212
|
+
with_dumper=False,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Add our addon
|
|
216
|
+
self._master.addons.add(self._create_addon())
|
|
217
|
+
|
|
218
|
+
# Run in thread
|
|
219
|
+
def run_proxy():
|
|
220
|
+
asyncio.set_event_loop(asyncio.new_event_loop())
|
|
221
|
+
self._master.run()
|
|
222
|
+
|
|
223
|
+
self._thread = threading.Thread(target=run_proxy, daemon=True)
|
|
224
|
+
self._thread.start()
|
|
225
|
+
self._running = True
|
|
226
|
+
|
|
227
|
+
logger.info(f"Proxy started on {self.config.listen_host}:{self.config.listen_port}")
|
|
228
|
+
return True
|
|
229
|
+
|
|
230
|
+
except Exception as e:
|
|
231
|
+
logger.error(f"Failed to start proxy: {e}")
|
|
232
|
+
return False
|
|
233
|
+
|
|
234
|
+
async def stop(self) -> None:
|
|
235
|
+
"""Stop the proxy server"""
|
|
236
|
+
if self._master:
|
|
237
|
+
self._master.shutdown()
|
|
238
|
+
self._running = False
|
|
239
|
+
logger.info("Proxy stopped")
|
|
240
|
+
|
|
241
|
+
def _create_addon(self) -> Any:
|
|
242
|
+
"""Create mitmproxy addon"""
|
|
243
|
+
interceptor = self
|
|
244
|
+
|
|
245
|
+
class AIPTAddon:
|
|
246
|
+
def request(self, flow: http.HTTPFlow) -> None:
|
|
247
|
+
# Check scope
|
|
248
|
+
if not interceptor._in_scope(flow.request.host):
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
interceptor._request_count += 1
|
|
252
|
+
request_id = f"req_{interceptor._request_count}"
|
|
253
|
+
|
|
254
|
+
# Create intercepted request
|
|
255
|
+
req = InterceptedRequest(
|
|
256
|
+
id=request_id,
|
|
257
|
+
timestamp=datetime.utcnow(),
|
|
258
|
+
method=flow.request.method,
|
|
259
|
+
url=flow.request.url,
|
|
260
|
+
host=flow.request.host,
|
|
261
|
+
path=flow.request.path,
|
|
262
|
+
headers=dict(flow.request.headers),
|
|
263
|
+
body=flow.request.content or b"",
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Parse content type
|
|
267
|
+
content_type = flow.request.headers.get("content-type", "")
|
|
268
|
+
req.content_type = content_type
|
|
269
|
+
req.is_json = "application/json" in content_type
|
|
270
|
+
req.is_form = "application/x-www-form-urlencoded" in content_type
|
|
271
|
+
req.is_multipart = "multipart/form-data" in content_type
|
|
272
|
+
|
|
273
|
+
# Parse cookies
|
|
274
|
+
if "cookie" in flow.request.headers:
|
|
275
|
+
for cookie in flow.request.headers["cookie"].split(";"):
|
|
276
|
+
if "=" in cookie:
|
|
277
|
+
name, value = cookie.strip().split("=", 1)
|
|
278
|
+
req.cookies[name] = value
|
|
279
|
+
|
|
280
|
+
# Store and notify
|
|
281
|
+
interceptor._requests[request_id] = req
|
|
282
|
+
flow.metadata["aipt_request_id"] = request_id
|
|
283
|
+
|
|
284
|
+
# Call callbacks
|
|
285
|
+
for callback in interceptor._request_callbacks:
|
|
286
|
+
try:
|
|
287
|
+
modified = callback(req)
|
|
288
|
+
if modified:
|
|
289
|
+
# Apply modifications to flow
|
|
290
|
+
flow.request.headers = http.Headers([(k, v) for k, v in modified.headers.items()])
|
|
291
|
+
if modified.body:
|
|
292
|
+
flow.request.content = modified.body
|
|
293
|
+
except Exception as e:
|
|
294
|
+
logger.error(f"Request callback error: {e}")
|
|
295
|
+
|
|
296
|
+
# Apply configured modifications
|
|
297
|
+
for header, value in interceptor.config.inject_headers.items():
|
|
298
|
+
flow.request.headers[header] = value
|
|
299
|
+
for header in interceptor.config.remove_headers:
|
|
300
|
+
if header in flow.request.headers:
|
|
301
|
+
del flow.request.headers[header]
|
|
302
|
+
|
|
303
|
+
def response(self, flow: http.HTTPFlow) -> None:
|
|
304
|
+
request_id = flow.metadata.get("aipt_request_id")
|
|
305
|
+
if not request_id:
|
|
306
|
+
return
|
|
307
|
+
|
|
308
|
+
# Get request for timing
|
|
309
|
+
request = interceptor._requests.get(request_id)
|
|
310
|
+
|
|
311
|
+
# Create intercepted response
|
|
312
|
+
resp = InterceptedResponse(
|
|
313
|
+
request_id=request_id,
|
|
314
|
+
timestamp=datetime.utcnow(),
|
|
315
|
+
status_code=flow.response.status_code,
|
|
316
|
+
reason=flow.response.reason or "",
|
|
317
|
+
headers=dict(flow.response.headers),
|
|
318
|
+
body=flow.response.content or b"",
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
# Calculate response time
|
|
322
|
+
if request:
|
|
323
|
+
resp.response_time_ms = (resp.timestamp - request.timestamp).total_seconds() * 1000
|
|
324
|
+
|
|
325
|
+
# Parse content type
|
|
326
|
+
content_type = flow.response.headers.get("content-type", "")
|
|
327
|
+
resp.content_type = content_type
|
|
328
|
+
resp.is_json = "application/json" in content_type
|
|
329
|
+
resp.is_html = "text/html" in content_type
|
|
330
|
+
|
|
331
|
+
# Store
|
|
332
|
+
interceptor._responses[request_id] = resp
|
|
333
|
+
|
|
334
|
+
# Call callbacks
|
|
335
|
+
for callback in interceptor._response_callbacks:
|
|
336
|
+
try:
|
|
337
|
+
modified = callback(resp)
|
|
338
|
+
if modified:
|
|
339
|
+
flow.response.headers = http.Headers([(k, v) for k, v in modified.headers.items()])
|
|
340
|
+
if modified.body:
|
|
341
|
+
flow.response.content = modified.body
|
|
342
|
+
except Exception as e:
|
|
343
|
+
logger.error(f"Response callback error: {e}")
|
|
344
|
+
|
|
345
|
+
return AIPTAddon()
|
|
346
|
+
|
|
347
|
+
def _in_scope(self, host: str) -> bool:
|
|
348
|
+
"""Check if host is in scope"""
|
|
349
|
+
# Check excludes
|
|
350
|
+
for pattern in self.config.exclude_hosts:
|
|
351
|
+
if self._host_matches(host, pattern):
|
|
352
|
+
return False
|
|
353
|
+
|
|
354
|
+
# If includes specified, host must match
|
|
355
|
+
if self.config.include_hosts:
|
|
356
|
+
for pattern in self.config.include_hosts:
|
|
357
|
+
if self._host_matches(host, pattern):
|
|
358
|
+
return True
|
|
359
|
+
return False
|
|
360
|
+
|
|
361
|
+
return True
|
|
362
|
+
|
|
363
|
+
def _host_matches(self, host: str, pattern: str) -> bool:
|
|
364
|
+
"""Check if host matches pattern"""
|
|
365
|
+
if pattern.startswith("*."):
|
|
366
|
+
return host.endswith(pattern[1:]) or host == pattern[2:]
|
|
367
|
+
return host == pattern
|
|
368
|
+
|
|
369
|
+
def get_requests(self) -> list[InterceptedRequest]:
|
|
370
|
+
"""Get all captured requests"""
|
|
371
|
+
return list(self._requests.values())
|
|
372
|
+
|
|
373
|
+
def get_responses(self) -> list[InterceptedResponse]:
|
|
374
|
+
"""Get all captured responses"""
|
|
375
|
+
return list(self._responses.values())
|
|
376
|
+
|
|
377
|
+
def get_request(self, request_id: str) -> Optional[InterceptedRequest]:
|
|
378
|
+
"""Get specific request by ID"""
|
|
379
|
+
return self._requests.get(request_id)
|
|
380
|
+
|
|
381
|
+
def get_response(self, request_id: str) -> Optional[InterceptedResponse]:
|
|
382
|
+
"""Get response for a request"""
|
|
383
|
+
return self._responses.get(request_id)
|
|
384
|
+
|
|
385
|
+
def clear_history(self) -> None:
|
|
386
|
+
"""Clear captured traffic"""
|
|
387
|
+
self._requests.clear()
|
|
388
|
+
self._responses.clear()
|
|
389
|
+
|
|
390
|
+
def export_har(self, filepath: str) -> bool:
|
|
391
|
+
"""Export traffic to HAR format"""
|
|
392
|
+
try:
|
|
393
|
+
har = {
|
|
394
|
+
"log": {
|
|
395
|
+
"version": "1.2",
|
|
396
|
+
"creator": {"name": "AIPT", "version": "2.0"},
|
|
397
|
+
"entries": [],
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
for req_id, request in self._requests.items():
|
|
402
|
+
response = self._responses.get(req_id)
|
|
403
|
+
|
|
404
|
+
entry = {
|
|
405
|
+
"startedDateTime": request.timestamp.isoformat(),
|
|
406
|
+
"request": {
|
|
407
|
+
"method": request.method,
|
|
408
|
+
"url": request.url,
|
|
409
|
+
"headers": [{"name": k, "value": v} for k, v in request.headers.items()],
|
|
410
|
+
"queryString": [],
|
|
411
|
+
"bodySize": len(request.body),
|
|
412
|
+
},
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if response:
|
|
416
|
+
entry["response"] = {
|
|
417
|
+
"status": response.status_code,
|
|
418
|
+
"statusText": response.reason,
|
|
419
|
+
"headers": [{"name": k, "value": v} for k, v in response.headers.items()],
|
|
420
|
+
"content": {
|
|
421
|
+
"size": len(response.body),
|
|
422
|
+
"mimeType": response.content_type,
|
|
423
|
+
},
|
|
424
|
+
}
|
|
425
|
+
entry["time"] = response.response_time_ms
|
|
426
|
+
|
|
427
|
+
har["log"]["entries"].append(entry)
|
|
428
|
+
|
|
429
|
+
with open(filepath, "w") as f:
|
|
430
|
+
json.dump(har, f, indent=2)
|
|
431
|
+
|
|
432
|
+
return True
|
|
433
|
+
except Exception as e:
|
|
434
|
+
logger.error(f"HAR export error: {e}")
|
|
435
|
+
return False
|
|
436
|
+
|
|
437
|
+
@property
|
|
438
|
+
def is_running(self) -> bool:
|
|
439
|
+
return self._running
|
|
440
|
+
|
|
441
|
+
@property
|
|
442
|
+
def request_count(self) -> int:
|
|
443
|
+
return self._request_count
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
# Convenience function for simple proxy usage
|
|
447
|
+
def create_proxy(port: int = 8080, hosts: Optional[list[str]] = None) -> ProxyInterceptor:
|
|
448
|
+
"""Create a configured proxy"""
|
|
449
|
+
config = ProxyConfig(listen_port=port)
|
|
450
|
+
if hosts:
|
|
451
|
+
config.include_hosts = hosts
|
|
452
|
+
return ProxyInterceptor(config)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT Recon Module
|
|
3
|
+
|
|
4
|
+
Reconnaissance and information gathering:
|
|
5
|
+
- Subdomain enumeration
|
|
6
|
+
- Port scanning
|
|
7
|
+
- Technology detection
|
|
8
|
+
- DNS analysis
|
|
9
|
+
- Whois lookups
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .subdomain import (
|
|
13
|
+
SubdomainEnumerator,
|
|
14
|
+
SubdomainConfig,
|
|
15
|
+
SubdomainResult,
|
|
16
|
+
)
|
|
17
|
+
from .tech_detect import (
|
|
18
|
+
TechDetector,
|
|
19
|
+
Technology,
|
|
20
|
+
TechStack,
|
|
21
|
+
)
|
|
22
|
+
from .dns import (
|
|
23
|
+
DNSAnalyzer,
|
|
24
|
+
DNSRecord,
|
|
25
|
+
DNSResult,
|
|
26
|
+
)
|
|
27
|
+
from .osint import (
|
|
28
|
+
OSINTCollector,
|
|
29
|
+
OSINTResult,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"SubdomainEnumerator",
|
|
34
|
+
"SubdomainConfig",
|
|
35
|
+
"SubdomainResult",
|
|
36
|
+
"TechDetector",
|
|
37
|
+
"Technology",
|
|
38
|
+
"TechStack",
|
|
39
|
+
"DNSAnalyzer",
|
|
40
|
+
"DNSRecord",
|
|
41
|
+
"DNSResult",
|
|
42
|
+
"OSINTCollector",
|
|
43
|
+
"OSINTResult",
|
|
44
|
+
]
|