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.
Files changed (29) hide show
  1. changedetection_io_osint_processor-0.0.1.dist-info/METADATA +274 -0
  2. changedetection_io_osint_processor-0.0.1.dist-info/RECORD +29 -0
  3. changedetection_io_osint_processor-0.0.1.dist-info/WHEEL +5 -0
  4. changedetection_io_osint_processor-0.0.1.dist-info/entry_points.txt +2 -0
  5. changedetection_io_osint_processor-0.0.1.dist-info/licenses/LICENSE +661 -0
  6. changedetection_io_osint_processor-0.0.1.dist-info/top_level.txt +1 -0
  7. changedetectionio_osint/__init__.py +22 -0
  8. changedetectionio_osint/forms.py +289 -0
  9. changedetectionio_osint/plugin.py +37 -0
  10. changedetectionio_osint/processor.py +655 -0
  11. changedetectionio_osint/steps/__init__.py +4 -0
  12. changedetectionio_osint/steps/base.py +76 -0
  13. changedetectionio_osint/steps/bgp.py +88 -0
  14. changedetectionio_osint/steps/dns.py +147 -0
  15. changedetectionio_osint/steps/dns_scan.py +88 -0
  16. changedetectionio_osint/steps/dnssec.py +260 -0
  17. changedetectionio_osint/steps/email_security.py +236 -0
  18. changedetectionio_osint/steps/http_fingerprint.py +359 -0
  19. changedetectionio_osint/steps/http_scan.py +31 -0
  20. changedetectionio_osint/steps/mac_lookup.py +209 -0
  21. changedetectionio_osint/steps/os_detection.py +245 -0
  22. changedetectionio_osint/steps/portscan.py +113 -0
  23. changedetectionio_osint/steps/registry.py +49 -0
  24. changedetectionio_osint/steps/smtp_fingerprint.py +517 -0
  25. changedetectionio_osint/steps/ssh_fingerprint.py +310 -0
  26. changedetectionio_osint/steps/tls_analysis.py +332 -0
  27. changedetectionio_osint/steps/traceroute.py +127 -0
  28. changedetectionio_osint/steps/whois_lookup.py +125 -0
  29. changedetectionio_osint/steps/whois_scan.py +123 -0
@@ -0,0 +1,127 @@
1
+ """
2
+ Traceroute Step
3
+ Performs traceroute and displays last N hops to target
4
+ """
5
+
6
+ import asyncio
7
+ # SOCKS5 proxy support: Traceroute uses ICMP/UDP, incompatible with SOCKS5
8
+ supports_socks5 = False
9
+ import socket
10
+ import struct
11
+ from loguru import logger
12
+
13
+ # Configuration: Number of last hops to display
14
+ TRACEROUTE_LAST_HOPS = 3
15
+
16
+ # Maximum TTL to try
17
+ MAX_TTL = 30
18
+
19
+
20
+ async def scan_traceroute(ip_address, dns_resolver, last_n_hops=TRACEROUTE_LAST_HOPS, watch_uuid=None, update_signal=None):
21
+ """
22
+ Perform traceroute and return last N hops
23
+
24
+ Args:
25
+ ip_address: Target IP address
26
+ dns_resolver: DNS resolver for reverse lookups
27
+ last_n_hops: Number of last hops to return (default: 3)
28
+ watch_uuid: Optional watch UUID for status updates
29
+ update_signal: Optional blinker signal for status updates
30
+
31
+ Returns:
32
+ list: List of last N hops with hop number, IP, and hostname
33
+ """
34
+ if update_signal and watch_uuid:
35
+ update_signal.send(watch_uuid=watch_uuid, status="Trace")
36
+
37
+ def run_traceroute():
38
+ """Perform traceroute using ICMP (requires raw sockets) or UDP fallback"""
39
+ import subprocess
40
+ import re
41
+
42
+ try:
43
+ # Use system traceroute command (works without root for UDP)
44
+ # -n: no DNS resolution (we'll do it ourselves)
45
+ # -m: max hops
46
+ # -w: timeout per hop
47
+ # -q: queries per hop
48
+ result = subprocess.run(
49
+ ['traceroute', '-n', '-m', str(MAX_TTL), '-w', '1', '-q', '1', ip_address],
50
+ capture_output=True,
51
+ text=True,
52
+ timeout=15
53
+ )
54
+
55
+ if result.returncode != 0:
56
+ logger.warning(f"Traceroute command failed: {result.stderr}")
57
+ return []
58
+
59
+ # Parse traceroute output
60
+ hops = []
61
+ lines = result.stdout.strip().split('\n')
62
+
63
+ for line in lines[1:]: # Skip header line
64
+ # Parse line like: " 1 192.168.1.1 1.234 ms"
65
+ match = re.search(r'^\s*(\d+)\s+([\d\.]+|[\da-f:]+)\s+', line)
66
+ if match:
67
+ hop_num = int(match.group(1))
68
+ hop_ip = match.group(2)
69
+
70
+ # Skip if it's a timeout (* * *)
71
+ if '*' in line and hop_ip not in line:
72
+ continue
73
+
74
+ # Reverse DNS lookup for this hop
75
+ try:
76
+ import dns.reversename
77
+ rev_name = dns.reversename.from_address(hop_ip)
78
+ answers = dns_resolver.resolve(rev_name, 'PTR')
79
+ hostname = str(answers[0])
80
+ except:
81
+ hostname = ""
82
+
83
+ hops.append({
84
+ 'hop': hop_num,
85
+ 'ip': hop_ip,
86
+ 'hostname': hostname
87
+ })
88
+
89
+ # Return only last N hops
90
+ if hops:
91
+ return hops[-last_n_hops:]
92
+ return []
93
+
94
+ except subprocess.TimeoutExpired:
95
+ logger.warning("Traceroute timed out")
96
+ return []
97
+ except FileNotFoundError:
98
+ logger.warning("traceroute command not found, skipping traceroute")
99
+ return []
100
+ except Exception as e:
101
+ logger.error(f"Traceroute failed: {e}")
102
+ return []
103
+
104
+ try:
105
+ return await asyncio.to_thread(run_traceroute)
106
+ except Exception as e:
107
+ logger.error(f"Traceroute scan failed: {e}")
108
+ return []
109
+
110
+
111
+ def format_traceroute_results(hops):
112
+ """Format traceroute results for output"""
113
+ lines = []
114
+ lines.append(f"=== Traceroute (Last {TRACEROUTE_LAST_HOPS} Hops) ===")
115
+
116
+ if hops:
117
+ for hop in hops:
118
+ if hop['hostname']:
119
+ lines.append(f" {hop['hop']:2d}. {hop['ip']:15s} ({hop['hostname']})")
120
+ else:
121
+ lines.append(f" {hop['hop']:2d}. {hop['ip']:15s}")
122
+ else:
123
+ lines.append("Traceroute data not available")
124
+ lines.append("(requires traceroute command or may be blocked by firewall)")
125
+
126
+ lines.append("")
127
+ return '\n'.join(lines)
@@ -0,0 +1,125 @@
1
+ """
2
+ WHOIS Lookup Step
3
+ Performs WHOIS queries using python-whois library
4
+ """
5
+
6
+ import asyncio
7
+ # SOCKS5 proxy support: python-whois library doesn't support SOCKS5 (TODO: implement raw WHOIS over SOCKS5)
8
+ supports_socks5 = False
9
+ from datetime import datetime, timezone
10
+ from loguru import logger
11
+
12
+
13
+ async def scan_whois(hostname, watch_uuid=None, update_signal=None):
14
+ """
15
+ Perform WHOIS lookup on hostname
16
+
17
+ Args:
18
+ hostname: Target hostname
19
+ watch_uuid: Optional watch UUID for status updates
20
+ update_signal: Optional blinker signal for status updates
21
+
22
+ Returns:
23
+ whois.WhoisEntry or None: WHOIS data
24
+ """
25
+ if update_signal and watch_uuid:
26
+ update_signal.send(watch_uuid=watch_uuid, status="WHOIS")
27
+
28
+ try:
29
+ import whois
30
+ return await asyncio.to_thread(whois.whois, hostname)
31
+ except Exception as e:
32
+ logger.error(f"WHOIS lookup failed: {e}")
33
+ return None
34
+
35
+
36
+ def format_whois_results(whois_data, expire_warning_days=3):
37
+ """Format WHOIS results for output
38
+
39
+ Args:
40
+ whois_data: WHOIS data object
41
+ expire_warning_days: Number of days before expiration to show warning (default: 3)
42
+ """
43
+ lines = []
44
+ lines.append("=== WHOIS Information ===")
45
+
46
+ if whois_data:
47
+ field_map = {
48
+ 'domain_name': 'Domain Name',
49
+ 'registrar': 'Registrar',
50
+ 'whois_server': 'WHOIS Server',
51
+ 'creation_date': 'Creation Date',
52
+ 'expiration_date': 'Expiration Date',
53
+ 'updated_date': 'Updated Date',
54
+ 'name_servers': 'Name Servers',
55
+ 'status': 'Status',
56
+ 'dnssec': 'DNSSEC',
57
+ 'org': 'Organization',
58
+ 'country': 'Country',
59
+ }
60
+
61
+ expiration_date = None
62
+ for key, label in field_map.items():
63
+ value = getattr(whois_data, key, None)
64
+ if value:
65
+ if isinstance(value, list):
66
+ # Take first item for dates, show all for name servers
67
+ if key in ['creation_date', 'expiration_date', 'updated_date']:
68
+ value = value[0] if value else None
69
+ if value:
70
+ lines.append(f"{label}: {value}")
71
+ # Track expiration date for countdown calculation
72
+ if key == 'expiration_date':
73
+ expiration_date = value
74
+ else:
75
+ lines.append(f"{label}:")
76
+ for item in value[:10]: # Limit to 10 items
77
+ lines.append(f" - {item}")
78
+ else:
79
+ lines.append(f"{label}: {value}")
80
+ # Track expiration date for countdown calculation
81
+ if key == 'expiration_date':
82
+ expiration_date = value
83
+
84
+ # Add expiration countdown if within configured warning days
85
+ if expiration_date and expire_warning_days > 0:
86
+ try:
87
+ # Ensure expiration_date is a datetime object
88
+ if not isinstance(expiration_date, datetime):
89
+ date_str = str(expiration_date)
90
+ # Try multiple parsing strategies
91
+ try:
92
+ # Try ISO format with Z replacement
93
+ expiration_date = datetime.fromisoformat(date_str.replace('Z', '+00:00'))
94
+ except (ValueError, AttributeError):
95
+ # Try parsing without timezone
96
+ from dateutil import parser
97
+ expiration_date = parser.parse(date_str)
98
+
99
+ # Make timezone-aware if needed
100
+ if expiration_date.tzinfo is None:
101
+ expiration_date = expiration_date.replace(tzinfo=timezone.utc)
102
+ # Convert to UTC if it's in a different timezone
103
+ elif expiration_date.tzinfo != timezone.utc:
104
+ expiration_date = expiration_date.astimezone(timezone.utc)
105
+
106
+ now = datetime.now(timezone.utc)
107
+ days_to_expire = (expiration_date - now).days
108
+
109
+ if days_to_expire <= expire_warning_days and days_to_expire >= 0:
110
+ if days_to_expire == 0:
111
+ lines.append("⚠️ WARNING: Domain expires TODAY!")
112
+ elif days_to_expire == 1:
113
+ lines.append("⚠️ WARNING: Domain expires in 1 day")
114
+ else:
115
+ lines.append(f"⚠️ WARNING: Domain expires in {days_to_expire} days")
116
+ elif days_to_expire < 0:
117
+ lines.append("⚠️ WARNING: Domain has EXPIRED!")
118
+ except Exception as e:
119
+ logger.error(f"Could not parse expiration date for countdown: {e}")
120
+ lines.append(f"⚠️ ERROR: Could not parse expiration date format: {e}")
121
+ else:
122
+ lines.append("No WHOIS data available")
123
+
124
+ lines.append("")
125
+ return '\n'.join(lines)
@@ -0,0 +1,123 @@
1
+ """
2
+ WHOIS Lookup Step
3
+ """
4
+
5
+ import asyncio
6
+ from datetime import datetime, timezone
7
+ from loguru import logger
8
+ from .base import ScanStep
9
+ from .registry import register_step
10
+
11
+
12
+ @register_step
13
+ class WHOISScanStep(ScanStep):
14
+ """WHOIS domain registration information"""
15
+
16
+ name = "WHOIS Information"
17
+ order = 20
18
+
19
+ async def scan(self, context: dict):
20
+ """Perform WHOIS lookup"""
21
+ hostname = context['hostname']
22
+ watch_uuid = context.get('watch_uuid')
23
+ update_signal = context.get('update_signal')
24
+
25
+ if update_signal and watch_uuid:
26
+ update_signal.send(watch_uuid=watch_uuid, status="WHOIS")
27
+
28
+ try:
29
+ import whois
30
+ return await asyncio.to_thread(whois.whois, hostname)
31
+ except Exception as e:
32
+ logger.error(f"WHOIS lookup failed: {e}")
33
+ return None
34
+
35
+ def format_results(self, whois_data, expire_warning_days=3):
36
+ """Format WHOIS results
37
+
38
+ Args:
39
+ whois_data: WHOIS data object
40
+ expire_warning_days: Number of days before expiration to show warning (default: 3)
41
+ """
42
+ lines = []
43
+ lines.append("=== WHOIS Information ===")
44
+
45
+ if whois_data and not isinstance(whois_data, Exception):
46
+ field_map = {
47
+ 'domain_name': 'Domain Name',
48
+ 'registrar': 'Registrar',
49
+ 'whois_server': 'WHOIS Server',
50
+ 'creation_date': 'Creation Date',
51
+ 'expiration_date': 'Expiration Date',
52
+ 'updated_date': 'Updated Date',
53
+ 'name_servers': 'Name Servers',
54
+ 'status': 'Status',
55
+ 'dnssec': 'DNSSEC',
56
+ 'org': 'Organization',
57
+ 'country': 'Country',
58
+ }
59
+
60
+ expiration_date = None
61
+ for key, label in field_map.items():
62
+ value = getattr(whois_data, key, None)
63
+ if value:
64
+ if isinstance(value, list):
65
+ if key in ['creation_date', 'expiration_date', 'updated_date']:
66
+ value = value[0] if value else None
67
+ if value:
68
+ lines.append(f"{label}: {value}")
69
+ # Track expiration date for countdown calculation
70
+ if key == 'expiration_date':
71
+ expiration_date = value
72
+ else:
73
+ lines.append(f"{label}:")
74
+ for item in value[:10]:
75
+ lines.append(f" - {item}")
76
+ else:
77
+ lines.append(f"{label}: {value}")
78
+ # Track expiration date for countdown calculation
79
+ if key == 'expiration_date':
80
+ expiration_date = value
81
+
82
+ # Add expiration countdown if within configured warning days
83
+ if expiration_date and expire_warning_days > 0:
84
+ try:
85
+ # Ensure expiration_date is a datetime object
86
+ if not isinstance(expiration_date, datetime):
87
+ date_str = str(expiration_date)
88
+ # Try multiple parsing strategies
89
+ try:
90
+ # Try ISO format with Z replacement
91
+ expiration_date = datetime.fromisoformat(date_str.replace('Z', '+00:00'))
92
+ except (ValueError, AttributeError):
93
+ # Try parsing without timezone
94
+ from dateutil import parser
95
+ expiration_date = parser.parse(date_str)
96
+
97
+ # Make timezone-aware if needed
98
+ if expiration_date.tzinfo is None:
99
+ expiration_date = expiration_date.replace(tzinfo=timezone.utc)
100
+ # Convert to UTC if it's in a different timezone
101
+ elif expiration_date.tzinfo != timezone.utc:
102
+ expiration_date = expiration_date.astimezone(timezone.utc)
103
+
104
+ now = datetime.now(timezone.utc)
105
+ days_to_expire = (expiration_date - now).days
106
+
107
+ if days_to_expire <= expire_warning_days and days_to_expire >= 0:
108
+ if days_to_expire == 0:
109
+ lines.append("⚠️ WARNING: Domain expires TODAY!")
110
+ elif days_to_expire == 1:
111
+ lines.append("⚠️ WARNING: Domain expires in 1 day")
112
+ else:
113
+ lines.append(f"⚠️ WARNING: Domain expires in {days_to_expire} days")
114
+ elif days_to_expire < 0:
115
+ lines.append("⚠️ WARNING: Domain has EXPIRED!")
116
+ except Exception as e:
117
+ logger.error(f"Could not parse expiration date for countdown: {e}")
118
+ lines.append(f"⚠️ ERROR: Could not parse expiration date format: {e}")
119
+ else:
120
+ lines.append("No WHOIS data available")
121
+
122
+ lines.append("")
123
+ return '\n'.join(lines)