changedetection.io-osint-processor 0.0.1__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.
- changedetection_io_osint_processor-0.0.1.dist-info/METADATA +274 -0
- changedetection_io_osint_processor-0.0.1.dist-info/RECORD +29 -0
- changedetection_io_osint_processor-0.0.1.dist-info/WHEEL +5 -0
- changedetection_io_osint_processor-0.0.1.dist-info/entry_points.txt +2 -0
- changedetection_io_osint_processor-0.0.1.dist-info/licenses/LICENSE +661 -0
- changedetection_io_osint_processor-0.0.1.dist-info/top_level.txt +1 -0
- changedetectionio_osint/__init__.py +22 -0
- changedetectionio_osint/forms.py +289 -0
- changedetectionio_osint/plugin.py +37 -0
- changedetectionio_osint/processor.py +655 -0
- changedetectionio_osint/steps/__init__.py +4 -0
- changedetectionio_osint/steps/base.py +76 -0
- changedetectionio_osint/steps/bgp.py +88 -0
- changedetectionio_osint/steps/dns.py +147 -0
- changedetectionio_osint/steps/dns_scan.py +88 -0
- changedetectionio_osint/steps/dnssec.py +260 -0
- changedetectionio_osint/steps/email_security.py +236 -0
- changedetectionio_osint/steps/http_fingerprint.py +359 -0
- changedetectionio_osint/steps/http_scan.py +31 -0
- changedetectionio_osint/steps/mac_lookup.py +209 -0
- changedetectionio_osint/steps/os_detection.py +245 -0
- changedetectionio_osint/steps/portscan.py +113 -0
- changedetectionio_osint/steps/registry.py +49 -0
- changedetectionio_osint/steps/smtp_fingerprint.py +517 -0
- changedetectionio_osint/steps/ssh_fingerprint.py +310 -0
- changedetectionio_osint/steps/tls_analysis.py +332 -0
- changedetectionio_osint/steps/traceroute.py +127 -0
- changedetectionio_osint/steps/whois_lookup.py +125 -0
- changedetectionio_osint/steps/whois_scan.py +123 -0
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OSINT Reconnaissance Processor - Configurable Execution Mode
|
|
3
|
+
|
|
4
|
+
Extends text_json_diff for comprehensive reconnaissance using:
|
|
5
|
+
- dnspython: Fast DNS queries
|
|
6
|
+
- python-whois: WHOIS lookups
|
|
7
|
+
- requests: HTTP response fingerprinting
|
|
8
|
+
- SSLyze: SSL/TLS certificate analysis
|
|
9
|
+
- asyncio: Fast parallel port scanning
|
|
10
|
+
- Traceroute: Network path analysis
|
|
11
|
+
- BGP: AS/routing information
|
|
12
|
+
|
|
13
|
+
Execution mode is configurable via MODE setting ("serial" or "parallel").
|
|
14
|
+
All DNS operations use the configured DNS_SERVER.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import ipaddress
|
|
19
|
+
import socket
|
|
20
|
+
from loguru import logger
|
|
21
|
+
from urllib.parse import urlparse
|
|
22
|
+
from requests.structures import CaseInsensitiveDict
|
|
23
|
+
from blinker import signal
|
|
24
|
+
|
|
25
|
+
# Import all scan step modules (alias to avoid conflicts with dnspython)
|
|
26
|
+
from .steps import dns as dns_step
|
|
27
|
+
from .steps import whois_lookup
|
|
28
|
+
from .steps import http_fingerprint
|
|
29
|
+
from .steps import tls_analysis
|
|
30
|
+
from .steps import portscan
|
|
31
|
+
from .steps import traceroute
|
|
32
|
+
from .steps import bgp as bgp_step
|
|
33
|
+
from .steps import mac_lookup
|
|
34
|
+
from .steps import os_detection
|
|
35
|
+
from .steps import email_security
|
|
36
|
+
from .steps import dnssec
|
|
37
|
+
from .steps import ssh_fingerprint
|
|
38
|
+
from .steps import smtp_fingerprint
|
|
39
|
+
|
|
40
|
+
# ============================================================================
|
|
41
|
+
# CONFIGURATION
|
|
42
|
+
# ============================================================================
|
|
43
|
+
|
|
44
|
+
# Execution Mode: "serial" or "parallel"
|
|
45
|
+
# serial: Steps run one after another (safer, easier to debug, default)
|
|
46
|
+
# parallel: All steps run simultaneously (faster, 4-5x speedup)
|
|
47
|
+
MODE = "serial"
|
|
48
|
+
|
|
49
|
+
# DNS Server Configuration
|
|
50
|
+
# This DNS server will be used for ALL DNS lookups
|
|
51
|
+
# TODO: Make this a user setting in the future
|
|
52
|
+
DNS_SERVER = "8.8.8.8"
|
|
53
|
+
|
|
54
|
+
# Import the text_json_diff processor to extend it
|
|
55
|
+
from changedetectionio.processors.text_json_diff.processor import perform_site_check as text_json_diff_processor
|
|
56
|
+
|
|
57
|
+
# Translation marker for extraction
|
|
58
|
+
def _(x): return x
|
|
59
|
+
name = _('OSINT Reconnaissance')
|
|
60
|
+
description = _('Comprehensive reconnaissance: DNS, DNSSEC, SPF/DMARC/DKIM, WHOIS, HTTP, TLS, SSH, SMTP, Ports, Traceroute, BGP')
|
|
61
|
+
del _
|
|
62
|
+
processor_weight = -50
|
|
63
|
+
list_badge_text = "OSINT"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class perform_site_check(text_json_diff_processor):
|
|
67
|
+
"""
|
|
68
|
+
OSINT Reconnaissance processor that extends text_json_diff.
|
|
69
|
+
|
|
70
|
+
Reconnaissance steps (configurable MODE: serial or parallel):
|
|
71
|
+
- DNS queries (A, AAAA, MX, NS, TXT, SOA, CAA)
|
|
72
|
+
- DNSSEC validation (cryptographic signatures, chain of trust)
|
|
73
|
+
- Email security (SPF, DMARC, DKIM records)
|
|
74
|
+
- WHOIS lookups
|
|
75
|
+
- HTTP response fingerprinting (CDN/WAF detection, redirect chains)
|
|
76
|
+
- SSL/TLS certificate analysis
|
|
77
|
+
- SSH fingerprinting (banner, version, host keys, algorithms)
|
|
78
|
+
- SMTP fingerprinting (encryption, authentication, capabilities)
|
|
79
|
+
- Port scanning
|
|
80
|
+
- Traceroute (last N hops)
|
|
81
|
+
- BGP/ASN information
|
|
82
|
+
|
|
83
|
+
All DNS operations use the configured DNS_SERVER for consistent resolution.
|
|
84
|
+
|
|
85
|
+
Configuration:
|
|
86
|
+
- MODE = "serial" (default) or "parallel" (4-5x faster)
|
|
87
|
+
- DNS_SERVER = "8.8.8.8" (used for all DNS operations)
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
async def call_browser(self, preferred_proxy_id=None):
|
|
91
|
+
"""
|
|
92
|
+
Override the browser call to perform OSINT reconnaissance.
|
|
93
|
+
|
|
94
|
+
Runs scan steps in serial (default) or parallel mode based on MODE setting.
|
|
95
|
+
Serial: Steps run sequentially, safer and easier to debug
|
|
96
|
+
Parallel: Steps run concurrently, 4-5x faster
|
|
97
|
+
"""
|
|
98
|
+
url = self.watch.link
|
|
99
|
+
watch_uuid = self.watch.get('uuid')
|
|
100
|
+
|
|
101
|
+
# Signal for status updates to UI
|
|
102
|
+
update_signal = signal('watch_small_status_comment')
|
|
103
|
+
|
|
104
|
+
# Load processor-specific configuration from watch data directory
|
|
105
|
+
processor_config = self.get_extra_watch_config('osint_recon.json')
|
|
106
|
+
|
|
107
|
+
# Get configuration values (with defaults)
|
|
108
|
+
scan_mode = processor_config.get('scan_mode', MODE)
|
|
109
|
+
dns_server = processor_config.get('dns_server', DNS_SERVER) or DNS_SERVER
|
|
110
|
+
|
|
111
|
+
# Get enabled/disabled scan steps (all enabled by default)
|
|
112
|
+
enable_dns = processor_config.get('enable_dns', True)
|
|
113
|
+
enable_whois = processor_config.get('enable_whois', True)
|
|
114
|
+
enable_http = processor_config.get('enable_http', True)
|
|
115
|
+
enable_tls = processor_config.get('enable_tls', True)
|
|
116
|
+
tls_vulnerability_scan = processor_config.get('tls_vulnerability_scan', False)
|
|
117
|
+
enable_portscan = processor_config.get('enable_portscan', False)
|
|
118
|
+
enable_traceroute = processor_config.get('enable_traceroute', True)
|
|
119
|
+
enable_bgp = processor_config.get('enable_bgp', True)
|
|
120
|
+
enable_os_detection = processor_config.get('enable_os_detection', True)
|
|
121
|
+
enable_email_security = processor_config.get('enable_email_security', True)
|
|
122
|
+
enable_dnssec = processor_config.get('enable_dnssec', True)
|
|
123
|
+
enable_ssh = processor_config.get('enable_ssh', True)
|
|
124
|
+
enable_smtp = processor_config.get('enable_smtp', True)
|
|
125
|
+
smtp_ehlo_hostname = processor_config.get('smtp_ehlo_hostname', 'localhost.localdomain') or 'localhost.localdomain'
|
|
126
|
+
whois_expire_warning_days = processor_config.get('whois_expire_warning_days', 3)
|
|
127
|
+
tls_expire_warning_days = processor_config.get('tls_expire_warning_days', 3)
|
|
128
|
+
|
|
129
|
+
logger.info(f"Running OSINT reconnaissance on {url} (mode: {scan_mode}, DNS: {dns_server})")
|
|
130
|
+
update_signal.send(watch_uuid=watch_uuid, status="Starting")
|
|
131
|
+
|
|
132
|
+
# Validate URL
|
|
133
|
+
if not url or not url.startswith(('http://', 'https://')):
|
|
134
|
+
raise ValueError(f"Invalid URL for OSINT: {url}")
|
|
135
|
+
|
|
136
|
+
# Parse URL to get hostname
|
|
137
|
+
parsed = urlparse(url)
|
|
138
|
+
hostname = parsed.netloc or parsed.hostname
|
|
139
|
+
if not hostname:
|
|
140
|
+
raise ValueError(f"Could not extract hostname from URL: {url}")
|
|
141
|
+
|
|
142
|
+
port = parsed.port or (443 if parsed.scheme == 'https' else 80)
|
|
143
|
+
|
|
144
|
+
# Get proxy configuration if available
|
|
145
|
+
proxy_url = None
|
|
146
|
+
if preferred_proxy_id:
|
|
147
|
+
proxy_url = self.datastore.proxy_list.get(preferred_proxy_id).get('url')
|
|
148
|
+
|
|
149
|
+
# OSINT plugin ONLY supports SOCKS5 proxies
|
|
150
|
+
# HTTP/HTTPS proxies don't work for raw socket operations (SSH, SMTP, port scan)
|
|
151
|
+
if proxy_url and proxy_url.strip(): # Check if proxy is set and not empty
|
|
152
|
+
if not proxy_url.lower().startswith('socks5://'):
|
|
153
|
+
raise ValueError(
|
|
154
|
+
f"OSINT Reconnaissance processor only supports SOCKS5 proxies. "
|
|
155
|
+
f"Got '{proxy_url}' but expected 'socks5://...'. "
|
|
156
|
+
f"HTTP/HTTPS proxies are not supported for raw socket operations (SSH, SMTP, port scanning). "
|
|
157
|
+
f"Please configure a SOCKS5 proxy or use 'No proxy' for this watch."
|
|
158
|
+
)
|
|
159
|
+
logger.info(f"Using SOCKS5 proxy '{proxy_url}' for OSINT reconnaissance")
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
import dns.resolver
|
|
163
|
+
import dns.reversename
|
|
164
|
+
|
|
165
|
+
# Create DNS resolver with configured DNS server
|
|
166
|
+
dns_resolver = dns.resolver.Resolver()
|
|
167
|
+
dns_resolver.nameservers = [dns_server]
|
|
168
|
+
logger.debug(f"Using DNS server: {dns_server} for all operations")
|
|
169
|
+
|
|
170
|
+
# Resolve IP address first (needed by several steps)
|
|
171
|
+
# Check if hostname is already an IP address
|
|
172
|
+
try:
|
|
173
|
+
ipaddress.ip_address(hostname)
|
|
174
|
+
# It's already an IP address, no DNS resolution needed
|
|
175
|
+
ip_address = hostname
|
|
176
|
+
logger.debug(f"Input is already an IP address: {ip_address}")
|
|
177
|
+
except ValueError:
|
|
178
|
+
# Not an IP address, needs DNS resolution
|
|
179
|
+
try:
|
|
180
|
+
answers = dns_resolver.resolve(hostname, 'A')
|
|
181
|
+
ip_address = str(answers[0])
|
|
182
|
+
except:
|
|
183
|
+
# Fallback to AAAA if no A record
|
|
184
|
+
try:
|
|
185
|
+
answers = dns_resolver.resolve(hostname, 'AAAA')
|
|
186
|
+
ip_address = str(answers[0])
|
|
187
|
+
except:
|
|
188
|
+
raise ValueError(f"Could not resolve hostname: {hostname}")
|
|
189
|
+
|
|
190
|
+
logger.debug(f"Resolved {hostname} to {ip_address}")
|
|
191
|
+
|
|
192
|
+
# Reverse DNS lookup
|
|
193
|
+
try:
|
|
194
|
+
rev_name = dns.reversename.from_address(ip_address)
|
|
195
|
+
answers = dns_resolver.resolve(rev_name, 'PTR')
|
|
196
|
+
reverse_dns = str(answers[0])
|
|
197
|
+
except:
|
|
198
|
+
reverse_dns = "No PTR record"
|
|
199
|
+
|
|
200
|
+
# =================================================================
|
|
201
|
+
# RUN SCAN STEPS (SERIAL OR PARALLEL BASED ON MODE)
|
|
202
|
+
# =================================================================
|
|
203
|
+
|
|
204
|
+
# Track which steps are skipped due to SOCKS5 incompatibility
|
|
205
|
+
skipped_steps = []
|
|
206
|
+
using_socks5_proxy = bool(proxy_url and proxy_url.strip())
|
|
207
|
+
|
|
208
|
+
if scan_mode == "parallel":
|
|
209
|
+
logger.info("Starting PARALLEL reconnaissance scans...")
|
|
210
|
+
|
|
211
|
+
# DNS must run first if SMTP is enabled (needs MX records)
|
|
212
|
+
# Run DNS first, then everything else in parallel
|
|
213
|
+
# Check SOCKS5 compatibility
|
|
214
|
+
if enable_dns:
|
|
215
|
+
if using_socks5_proxy and not dns_step.supports_socks5:
|
|
216
|
+
logger.info(f"Skipping DNS scan - does not support SOCKS5 proxy")
|
|
217
|
+
skipped_steps.append(("DNS Records", "DNS queries not compatible with SOCKS5"))
|
|
218
|
+
dns_results = None
|
|
219
|
+
else:
|
|
220
|
+
dns_results = await dns_step.scan_dns(hostname, dns_resolver, proxy_url, watch_uuid, update_signal)
|
|
221
|
+
else:
|
|
222
|
+
dns_results = None
|
|
223
|
+
|
|
224
|
+
# Get MX records for SMTP scanning
|
|
225
|
+
mx_records = []
|
|
226
|
+
if dns_results and isinstance(dns_results, dict):
|
|
227
|
+
mx_records = dns_results.get('MX', [])
|
|
228
|
+
|
|
229
|
+
# Build list of remaining scans to run in parallel
|
|
230
|
+
# Check SOCKS5 compatibility for each scan
|
|
231
|
+
scans = []
|
|
232
|
+
|
|
233
|
+
# WHOIS
|
|
234
|
+
if enable_whois:
|
|
235
|
+
if using_socks5_proxy and not whois_lookup.supports_socks5:
|
|
236
|
+
skipped_steps.append(("WHOIS Lookup", "WHOIS protocol (port 43) not compatible with SOCKS5"))
|
|
237
|
+
else:
|
|
238
|
+
scans.append(whois_lookup.scan_whois(hostname, watch_uuid, update_signal))
|
|
239
|
+
|
|
240
|
+
# HTTP Fingerprinting
|
|
241
|
+
if enable_http:
|
|
242
|
+
if using_socks5_proxy and not http_fingerprint.supports_socks5:
|
|
243
|
+
skipped_steps.append(("HTTP Fingerprinting", "HTTP client does not support SOCKS5"))
|
|
244
|
+
else:
|
|
245
|
+
scans.append(http_fingerprint.scan_http(url, dns_resolver, proxy_url, watch_uuid, update_signal))
|
|
246
|
+
|
|
247
|
+
# TLS Analysis
|
|
248
|
+
if enable_tls and parsed.scheme == 'https':
|
|
249
|
+
if using_socks5_proxy and not tls_analysis.supports_socks5:
|
|
250
|
+
skipped_steps.append(("SSL/TLS Analysis", "Direct TLS connections not compatible with SOCKS5"))
|
|
251
|
+
else:
|
|
252
|
+
scans.append(tls_analysis.scan_tls(hostname, port, watch_uuid, update_signal, tls_vulnerability_scan))
|
|
253
|
+
elif enable_tls:
|
|
254
|
+
scans.append(asyncio.sleep(0)) # Placeholder for TLS when not HTTPS
|
|
255
|
+
|
|
256
|
+
# Port Scanning
|
|
257
|
+
if enable_portscan:
|
|
258
|
+
if using_socks5_proxy and not portscan.supports_socks5:
|
|
259
|
+
skipped_steps.append(("Port Scanning", "Raw socket port scanning not compatible with SOCKS5"))
|
|
260
|
+
else:
|
|
261
|
+
scans.append(portscan.scan_ports(ip_address, None, watch_uuid, update_signal))
|
|
262
|
+
|
|
263
|
+
# Traceroute
|
|
264
|
+
if enable_traceroute:
|
|
265
|
+
if using_socks5_proxy and not traceroute.supports_socks5:
|
|
266
|
+
skipped_steps.append(("Traceroute", "ICMP/UDP traceroute not compatible with SOCKS5"))
|
|
267
|
+
else:
|
|
268
|
+
scans.append(traceroute.scan_traceroute(ip_address, dns_resolver, traceroute.TRACEROUTE_LAST_HOPS, watch_uuid, update_signal))
|
|
269
|
+
|
|
270
|
+
# BGP/ASN
|
|
271
|
+
if enable_bgp:
|
|
272
|
+
if using_socks5_proxy and not bgp_step.supports_socks5:
|
|
273
|
+
skipped_steps.append(("BGP/ASN Information", "BGP lookups not compatible with SOCKS5 proxy"))
|
|
274
|
+
else:
|
|
275
|
+
scans.append(bgp_step.scan_bgp(ip_address, watch_uuid, update_signal))
|
|
276
|
+
|
|
277
|
+
# OS Detection
|
|
278
|
+
if enable_os_detection:
|
|
279
|
+
if using_socks5_proxy and not os_detection.supports_socks5:
|
|
280
|
+
skipped_steps.append(("OS Detection", "TTL-based fingerprinting requires raw sockets, incompatible with SOCKS5"))
|
|
281
|
+
else:
|
|
282
|
+
scans.append(os_detection.scan_os(ip_address, watch_uuid, update_signal))
|
|
283
|
+
|
|
284
|
+
# Email Security
|
|
285
|
+
if enable_email_security:
|
|
286
|
+
if using_socks5_proxy and not email_security.supports_socks5:
|
|
287
|
+
skipped_steps.append(("Email Security (SPF/DMARC/DKIM)", "DNS-based queries (UDP) not compatible with SOCKS5"))
|
|
288
|
+
else:
|
|
289
|
+
scans.append(email_security.scan_email_security(hostname, dns_resolver, watch_uuid, update_signal))
|
|
290
|
+
|
|
291
|
+
# DNSSEC
|
|
292
|
+
if enable_dnssec:
|
|
293
|
+
if using_socks5_proxy and not dnssec.supports_socks5:
|
|
294
|
+
skipped_steps.append(("DNSSEC Validation", "DNS queries (UDP) not compatible with SOCKS5"))
|
|
295
|
+
else:
|
|
296
|
+
scans.append(dnssec.scan_dnssec(hostname, dns_resolver, watch_uuid, update_signal))
|
|
297
|
+
|
|
298
|
+
# SSH Fingerprinting
|
|
299
|
+
if enable_ssh:
|
|
300
|
+
if using_socks5_proxy and not ssh_fingerprint.supports_socks5:
|
|
301
|
+
skipped_steps.append(("SSH Fingerprinting", "SSH connections not compatible with SOCKS5"))
|
|
302
|
+
else:
|
|
303
|
+
scans.append(ssh_fingerprint.scan_ssh(hostname, 22, 5, proxy_url, watch_uuid, update_signal))
|
|
304
|
+
|
|
305
|
+
# SMTP Fingerprinting
|
|
306
|
+
if enable_smtp:
|
|
307
|
+
if using_socks5_proxy and not smtp_fingerprint.supports_socks5:
|
|
308
|
+
skipped_steps.append(("SMTP/Email Server Fingerprinting", "SMTP connections not compatible with SOCKS5"))
|
|
309
|
+
else:
|
|
310
|
+
scans.append(smtp_fingerprint.scan_smtp_mx_records(mx_records, dns_resolver, [25, 587, 465], 5, proxy_url, smtp_ehlo_hostname, watch_uuid, update_signal))
|
|
311
|
+
|
|
312
|
+
# MAC address lookup (always enabled for local network detection)
|
|
313
|
+
if using_socks5_proxy and not mac_lookup.supports_socks5:
|
|
314
|
+
skipped_steps.append(("MAC Address Lookup", "Layer 2 local network only, not compatible with SOCKS5"))
|
|
315
|
+
else:
|
|
316
|
+
scans.append(mac_lookup.scan_mac(ip_address, watch_uuid, update_signal))
|
|
317
|
+
|
|
318
|
+
# Launch all remaining scans concurrently
|
|
319
|
+
if scans:
|
|
320
|
+
results = await asyncio.gather(*scans, return_exceptions=True)
|
|
321
|
+
|
|
322
|
+
# Unpack results based on which scans were enabled
|
|
323
|
+
# Note: dns_results already populated above
|
|
324
|
+
idx = 0
|
|
325
|
+
whois_data = results[idx] if enable_whois else None
|
|
326
|
+
idx += 1 if enable_whois else 0
|
|
327
|
+
http_fingerprint_data = results[idx] if enable_http else None
|
|
328
|
+
idx += 1 if enable_http else 0
|
|
329
|
+
tls_results = results[idx] if enable_tls else None
|
|
330
|
+
idx += 1 if enable_tls else 0
|
|
331
|
+
open_ports = results[idx] if enable_portscan else None
|
|
332
|
+
idx += 1 if enable_portscan else 0
|
|
333
|
+
traceroute_hops = results[idx] if enable_traceroute else None
|
|
334
|
+
idx += 1 if enable_traceroute else 0
|
|
335
|
+
bgp_data = results[idx] if enable_bgp else None
|
|
336
|
+
idx += 1 if enable_bgp else 0
|
|
337
|
+
os_data = results[idx] if enable_os_detection else None
|
|
338
|
+
idx += 1 if enable_os_detection else 0
|
|
339
|
+
email_security_data = results[idx] if enable_email_security else None
|
|
340
|
+
idx += 1 if enable_email_security else 0
|
|
341
|
+
dnssec_data = results[idx] if enable_dnssec else None
|
|
342
|
+
idx += 1 if enable_dnssec else 0
|
|
343
|
+
ssh_data = results[idx] if enable_ssh else None
|
|
344
|
+
idx += 1 if enable_ssh else 0
|
|
345
|
+
smtp_data = results[idx] if enable_smtp else None
|
|
346
|
+
idx += 1 if enable_smtp else 0
|
|
347
|
+
mac_data = results[idx] # Always runs
|
|
348
|
+
else:
|
|
349
|
+
# No scans enabled
|
|
350
|
+
dns_results = whois_data = http_fingerprint_data = tls_results = None
|
|
351
|
+
open_ports = traceroute_hops = bgp_data = os_data = mac_data = None
|
|
352
|
+
email_security_data = dnssec_data = ssh_data = smtp_data = None
|
|
353
|
+
|
|
354
|
+
else: # scan_mode == "serial"
|
|
355
|
+
logger.info("Starting SERIAL reconnaissance scans...")
|
|
356
|
+
|
|
357
|
+
# Run each enabled step sequentially with SOCKS5 compatibility checks
|
|
358
|
+
|
|
359
|
+
# DNS
|
|
360
|
+
if enable_dns:
|
|
361
|
+
if using_socks5_proxy and not dns_step.supports_socks5:
|
|
362
|
+
skipped_steps.append(("DNS Records", "DNS queries not compatible with SOCKS5"))
|
|
363
|
+
dns_results = None
|
|
364
|
+
else:
|
|
365
|
+
dns_results = await dns_step.scan_dns(hostname, dns_resolver, proxy_url, watch_uuid, update_signal)
|
|
366
|
+
else:
|
|
367
|
+
dns_results = None
|
|
368
|
+
|
|
369
|
+
# Get MX records for SMTP scanning
|
|
370
|
+
mx_records = []
|
|
371
|
+
if dns_results and isinstance(dns_results, dict):
|
|
372
|
+
mx_records = dns_results.get('MX', [])
|
|
373
|
+
|
|
374
|
+
# WHOIS
|
|
375
|
+
if enable_whois:
|
|
376
|
+
if using_socks5_proxy and not whois_lookup.supports_socks5:
|
|
377
|
+
skipped_steps.append(("WHOIS Lookup", "WHOIS protocol (port 43) not compatible with SOCKS5"))
|
|
378
|
+
whois_data = None
|
|
379
|
+
else:
|
|
380
|
+
whois_data = await whois_lookup.scan_whois(hostname, watch_uuid, update_signal)
|
|
381
|
+
else:
|
|
382
|
+
whois_data = None
|
|
383
|
+
|
|
384
|
+
# HTTP Fingerprinting
|
|
385
|
+
if enable_http:
|
|
386
|
+
if using_socks5_proxy and not http_fingerprint.supports_socks5:
|
|
387
|
+
skipped_steps.append(("HTTP Fingerprinting", "HTTP client does not support SOCKS5"))
|
|
388
|
+
http_fingerprint_data = None
|
|
389
|
+
else:
|
|
390
|
+
http_fingerprint_data = await http_fingerprint.scan_http(url, dns_resolver, proxy_url, watch_uuid, update_signal)
|
|
391
|
+
else:
|
|
392
|
+
http_fingerprint_data = None
|
|
393
|
+
|
|
394
|
+
# TLS Analysis
|
|
395
|
+
if enable_tls and parsed.scheme == 'https':
|
|
396
|
+
if using_socks5_proxy and not tls_analysis.supports_socks5:
|
|
397
|
+
skipped_steps.append(("SSL/TLS Analysis", "Direct TLS connections not compatible with SOCKS5"))
|
|
398
|
+
tls_results = None
|
|
399
|
+
else:
|
|
400
|
+
tls_results = await tls_analysis.scan_tls(hostname, port, watch_uuid, update_signal, tls_vulnerability_scan)
|
|
401
|
+
else:
|
|
402
|
+
tls_results = None
|
|
403
|
+
|
|
404
|
+
# Port Scanning
|
|
405
|
+
if enable_portscan:
|
|
406
|
+
if using_socks5_proxy and not portscan.supports_socks5:
|
|
407
|
+
skipped_steps.append(("Port Scanning", "Raw socket port scanning not compatible with SOCKS5"))
|
|
408
|
+
open_ports = None
|
|
409
|
+
else:
|
|
410
|
+
open_ports = await portscan.scan_ports(ip_address, None, watch_uuid, update_signal)
|
|
411
|
+
else:
|
|
412
|
+
open_ports = None
|
|
413
|
+
|
|
414
|
+
# Traceroute
|
|
415
|
+
if enable_traceroute:
|
|
416
|
+
if using_socks5_proxy and not traceroute.supports_socks5:
|
|
417
|
+
skipped_steps.append(("Traceroute", "ICMP/UDP traceroute not compatible with SOCKS5"))
|
|
418
|
+
traceroute_hops = None
|
|
419
|
+
else:
|
|
420
|
+
traceroute_hops = await traceroute.scan_traceroute(ip_address, dns_resolver, traceroute.TRACEROUTE_LAST_HOPS, watch_uuid, update_signal)
|
|
421
|
+
else:
|
|
422
|
+
traceroute_hops = None
|
|
423
|
+
|
|
424
|
+
# BGP/ASN
|
|
425
|
+
if enable_bgp:
|
|
426
|
+
if using_socks5_proxy and not bgp_step.supports_socks5:
|
|
427
|
+
skipped_steps.append(("BGP/ASN Information", "BGP lookups not compatible with SOCKS5 proxy"))
|
|
428
|
+
bgp_data = None
|
|
429
|
+
else:
|
|
430
|
+
bgp_data = await bgp_step.scan_bgp(ip_address, watch_uuid, update_signal)
|
|
431
|
+
else:
|
|
432
|
+
bgp_data = None
|
|
433
|
+
|
|
434
|
+
# OS Detection
|
|
435
|
+
if enable_os_detection:
|
|
436
|
+
if using_socks5_proxy and not os_detection.supports_socks5:
|
|
437
|
+
skipped_steps.append(("OS Detection", "TTL-based fingerprinting requires raw sockets, incompatible with SOCKS5"))
|
|
438
|
+
os_data = None
|
|
439
|
+
else:
|
|
440
|
+
os_data = await os_detection.scan_os(ip_address, watch_uuid, update_signal)
|
|
441
|
+
else:
|
|
442
|
+
os_data = None
|
|
443
|
+
|
|
444
|
+
# Email Security
|
|
445
|
+
if enable_email_security:
|
|
446
|
+
if using_socks5_proxy and not email_security.supports_socks5:
|
|
447
|
+
skipped_steps.append(("Email Security (SPF/DMARC/DKIM)", "DNS-based queries (UDP) not compatible with SOCKS5"))
|
|
448
|
+
email_security_data = None
|
|
449
|
+
else:
|
|
450
|
+
email_security_data = await email_security.scan_email_security(hostname, dns_resolver, watch_uuid, update_signal)
|
|
451
|
+
else:
|
|
452
|
+
email_security_data = None
|
|
453
|
+
|
|
454
|
+
# DNSSEC
|
|
455
|
+
if enable_dnssec:
|
|
456
|
+
if using_socks5_proxy and not dnssec.supports_socks5:
|
|
457
|
+
skipped_steps.append(("DNSSEC Validation", "DNS queries (UDP) not compatible with SOCKS5"))
|
|
458
|
+
dnssec_data = None
|
|
459
|
+
else:
|
|
460
|
+
dnssec_data = await dnssec.scan_dnssec(hostname, dns_resolver, watch_uuid, update_signal)
|
|
461
|
+
else:
|
|
462
|
+
dnssec_data = None
|
|
463
|
+
|
|
464
|
+
# SSH Fingerprinting
|
|
465
|
+
if enable_ssh:
|
|
466
|
+
if using_socks5_proxy and not ssh_fingerprint.supports_socks5:
|
|
467
|
+
skipped_steps.append(("SSH Fingerprinting", "SSH connections not compatible with SOCKS5"))
|
|
468
|
+
ssh_data = None
|
|
469
|
+
else:
|
|
470
|
+
ssh_data = await ssh_fingerprint.scan_ssh(hostname, 22, 5, proxy_url, watch_uuid, update_signal)
|
|
471
|
+
else:
|
|
472
|
+
ssh_data = None
|
|
473
|
+
|
|
474
|
+
# SMTP Fingerprinting
|
|
475
|
+
if enable_smtp:
|
|
476
|
+
if using_socks5_proxy and not smtp_fingerprint.supports_socks5:
|
|
477
|
+
skipped_steps.append(("SMTP/Email Server Fingerprinting", "SMTP connections not compatible with SOCKS5"))
|
|
478
|
+
smtp_data = None
|
|
479
|
+
else:
|
|
480
|
+
smtp_data = await smtp_fingerprint.scan_smtp_mx_records(mx_records, dns_resolver, [25, 587, 465], 5, proxy_url, smtp_ehlo_hostname, watch_uuid, update_signal)
|
|
481
|
+
else:
|
|
482
|
+
smtp_data = None
|
|
483
|
+
|
|
484
|
+
# MAC address lookup (always enabled for local network detection)
|
|
485
|
+
if using_socks5_proxy and not mac_lookup.supports_socks5:
|
|
486
|
+
skipped_steps.append(("MAC Address Lookup", "Layer 2 local network only, not compatible with SOCKS5"))
|
|
487
|
+
mac_data = None
|
|
488
|
+
else:
|
|
489
|
+
mac_data = await mac_lookup.scan_mac(ip_address, watch_uuid, update_signal)
|
|
490
|
+
|
|
491
|
+
logger.info(f"All scans completed ({scan_mode} mode), formatting output...")
|
|
492
|
+
|
|
493
|
+
# =================================================================
|
|
494
|
+
# FORMAT ALL RESULTS
|
|
495
|
+
# =================================================================
|
|
496
|
+
|
|
497
|
+
# Build header
|
|
498
|
+
header_lines = []
|
|
499
|
+
header_lines.append(f"Target: {url}")
|
|
500
|
+
header_lines.append(f"Hostname: {hostname}")
|
|
501
|
+
header_lines.append(f"IP Address: {ip_address}")
|
|
502
|
+
header_lines.append(f"Reverse DNS: {reverse_dns}")
|
|
503
|
+
|
|
504
|
+
# Show proxy if configured
|
|
505
|
+
if proxy_url and proxy_url.strip():
|
|
506
|
+
header_lines.append(f"SOCKS5 Proxy: {proxy_url}")
|
|
507
|
+
|
|
508
|
+
# Add MAC address if available (local network only)
|
|
509
|
+
if mac_data and not isinstance(mac_data, Exception) and mac_data.get('mac_address'):
|
|
510
|
+
header_lines.append(f"MAC Address: {mac_data['mac_address']}")
|
|
511
|
+
if mac_data.get('vendor'):
|
|
512
|
+
header_lines.append(f"MAC Vendor: {mac_data['vendor']}")
|
|
513
|
+
|
|
514
|
+
# Show skipped steps if using SOCKS5 proxy
|
|
515
|
+
if skipped_steps:
|
|
516
|
+
header_lines.append("")
|
|
517
|
+
header_lines.append("⚠ STEPS SKIPPED DUE TO SOCKS5 PROXY:")
|
|
518
|
+
for step_name, reason in skipped_steps:
|
|
519
|
+
header_lines.append(f" ✗ {step_name} - {reason}")
|
|
520
|
+
|
|
521
|
+
header_lines.append("")
|
|
522
|
+
|
|
523
|
+
# Format all sections
|
|
524
|
+
sections = {}
|
|
525
|
+
|
|
526
|
+
# BGP/ASN section (shows first since it's network-level info)
|
|
527
|
+
if bgp_data and not isinstance(bgp_data, Exception):
|
|
528
|
+
sections["BGP / ASN Information"] = bgp_step.format_bgp_results(bgp_data)
|
|
529
|
+
|
|
530
|
+
# DNS section
|
|
531
|
+
if dns_results and not isinstance(dns_results, Exception):
|
|
532
|
+
sections["DNS Records"] = dns_step.format_dns_results(dns_results)
|
|
533
|
+
|
|
534
|
+
# DNSSEC section
|
|
535
|
+
if dnssec_data and not isinstance(dnssec_data, Exception):
|
|
536
|
+
sections["DNSSEC Validation"] = dnssec.format_dnssec_results(dnssec_data)
|
|
537
|
+
|
|
538
|
+
# Email Security section
|
|
539
|
+
if email_security_data and not isinstance(email_security_data, Exception):
|
|
540
|
+
sections["Email Security (SPF/DMARC/DKIM)"] = email_security.format_email_security_results(email_security_data)
|
|
541
|
+
|
|
542
|
+
# WHOIS section
|
|
543
|
+
if whois_data and not isinstance(whois_data, Exception):
|
|
544
|
+
sections["WHOIS Information"] = whois_lookup.format_whois_results(whois_data, whois_expire_warning_days)
|
|
545
|
+
|
|
546
|
+
# HTTP fingerprint section
|
|
547
|
+
if http_fingerprint_data and not isinstance(http_fingerprint_data, Exception):
|
|
548
|
+
sections["HTTP Response Fingerprint"] = http_fingerprint.format_http_results(http_fingerprint_data, parsed)
|
|
549
|
+
|
|
550
|
+
# TLS/SSL section (only for HTTPS)
|
|
551
|
+
if parsed.scheme == 'https' and tls_results and not isinstance(tls_results, Exception) and tls_results:
|
|
552
|
+
sections["SSL/TLS Analysis (SSLyze)"] = tls_analysis.format_tls_results(tls_results, tls_expire_warning_days, watch_uuid, update_signal)
|
|
553
|
+
|
|
554
|
+
# SSH fingerprint section
|
|
555
|
+
if ssh_data and not isinstance(ssh_data, Exception):
|
|
556
|
+
sections["SSH Server Fingerprint"] = ssh_fingerprint.format_ssh_results(ssh_data, 22)
|
|
557
|
+
|
|
558
|
+
# SMTP fingerprint section
|
|
559
|
+
if smtp_data and not isinstance(smtp_data, Exception):
|
|
560
|
+
sections["SMTP/Email Server Fingerprint"] = smtp_fingerprint.format_smtp_results(smtp_data)
|
|
561
|
+
|
|
562
|
+
# Traceroute section
|
|
563
|
+
if traceroute_hops and not isinstance(traceroute_hops, Exception):
|
|
564
|
+
sections["Traceroute (Last N Hops)"] = traceroute.format_traceroute_results(traceroute_hops)
|
|
565
|
+
|
|
566
|
+
# Port scan section
|
|
567
|
+
if open_ports is not None and not isinstance(open_ports, Exception):
|
|
568
|
+
sections["Port Scan (Common Ports)"] = portscan.format_portscan_results(open_ports)
|
|
569
|
+
|
|
570
|
+
# OS detection section
|
|
571
|
+
if os_data and not isinstance(os_data, Exception):
|
|
572
|
+
sections["Operating System Detection"] = os_detection.format_os_results(os_data)
|
|
573
|
+
|
|
574
|
+
# Combine header and sorted sections
|
|
575
|
+
output_parts = header_lines.copy()
|
|
576
|
+
|
|
577
|
+
# Sort sections alphabetically and append
|
|
578
|
+
for section_name in sorted(sections.keys()):
|
|
579
|
+
output_parts.append(sections[section_name])
|
|
580
|
+
|
|
581
|
+
output = '\n'.join(output_parts)
|
|
582
|
+
|
|
583
|
+
# Format output for better readability
|
|
584
|
+
output = self._format_output_for_diff(output, url)
|
|
585
|
+
|
|
586
|
+
# Set the content in our fetcher
|
|
587
|
+
self.fetcher.content = output
|
|
588
|
+
|
|
589
|
+
# Set some basic headers
|
|
590
|
+
if not hasattr(self.fetcher, 'headers') or self.fetcher.headers is None or not isinstance(self.fetcher.headers, dict):
|
|
591
|
+
self.fetcher.headers = CaseInsensitiveDict()
|
|
592
|
+
elif not isinstance(self.fetcher.headers, CaseInsensitiveDict):
|
|
593
|
+
self.fetcher.headers = CaseInsensitiveDict(self.fetcher.headers)
|
|
594
|
+
|
|
595
|
+
self.fetcher.headers['content-type'] = 'text/plain; charset=utf-8'
|
|
596
|
+
|
|
597
|
+
# Mark as successful fetch
|
|
598
|
+
if not hasattr(self.fetcher, 'status_code') or self.fetcher.status_code is None:
|
|
599
|
+
self.fetcher.status_code = 200
|
|
600
|
+
|
|
601
|
+
update_signal.send(watch_uuid=watch_uuid, status="Done")
|
|
602
|
+
logger.info(f"OSINT reconnaissance completed successfully, captured {len(output)} bytes")
|
|
603
|
+
|
|
604
|
+
except ImportError as e:
|
|
605
|
+
error_msg = (
|
|
606
|
+
f"Required packages not found. Please install them:\n"
|
|
607
|
+
f" pip install dnspython python-whois sslyze\n"
|
|
608
|
+
f"Error: {str(e)}"
|
|
609
|
+
)
|
|
610
|
+
logger.error(error_msg)
|
|
611
|
+
raise Exception(error_msg)
|
|
612
|
+
|
|
613
|
+
except Exception as e:
|
|
614
|
+
import traceback
|
|
615
|
+
logger.error(f"OSINT reconnaissance error: {str(e)}")
|
|
616
|
+
logger.error(f"Traceback: {traceback.format_exc()}")
|
|
617
|
+
raise
|
|
618
|
+
|
|
619
|
+
@staticmethod
|
|
620
|
+
def _format_output_for_diff(text, url):
|
|
621
|
+
"""
|
|
622
|
+
Format OSINT output for better readability in diff view.
|
|
623
|
+
"""
|
|
624
|
+
lines = []
|
|
625
|
+
|
|
626
|
+
# Add header
|
|
627
|
+
lines.append("=" * 70)
|
|
628
|
+
lines.append(f"OSINT Reconnaissance Report ({MODE.capitalize()} Mode)")
|
|
629
|
+
lines.append("=" * 70)
|
|
630
|
+
lines.append(f"Target URL: {url}")
|
|
631
|
+
lines.append("=" * 70)
|
|
632
|
+
lines.append("")
|
|
633
|
+
|
|
634
|
+
# Process the output
|
|
635
|
+
for line in text.split('\n'):
|
|
636
|
+
if line.startswith('==='):
|
|
637
|
+
# Section headers
|
|
638
|
+
lines.append("")
|
|
639
|
+
lines.append("-" * 70)
|
|
640
|
+
lines.append(line.replace('===', '##'))
|
|
641
|
+
lines.append("-" * 70)
|
|
642
|
+
else:
|
|
643
|
+
# Indent content for readability
|
|
644
|
+
if line and not line.startswith(('Target:', 'Hostname:', 'IP Address:')):
|
|
645
|
+
lines.append(f" {line}")
|
|
646
|
+
else:
|
|
647
|
+
lines.append(line)
|
|
648
|
+
|
|
649
|
+
# Add footer
|
|
650
|
+
lines.append("")
|
|
651
|
+
lines.append("=" * 70)
|
|
652
|
+
lines.append("End of Report")
|
|
653
|
+
lines.append("=" * 70)
|
|
654
|
+
|
|
655
|
+
return '\n'.join(lines)
|