reconforge 1.0.0__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.
reconforge/__init__.py ADDED
@@ -0,0 +1,29 @@
1
+ """ReconForge - AI-assisted recon toolkit for bug bounty hunters."""
2
+
3
+ __version__ = "1.0.0"
4
+ __author__ = "Feras"
5
+ __license__ = "MIT"
6
+
7
+ from .cache import ReconCache, get_cache
8
+ from .cli import cli
9
+ from .config import Config, get_config
10
+ from .logger import ReconForgeLogger, get_logger
11
+ from .portscan import scan_port, scan_ports
12
+ from .scopecheck import check_targets
13
+ from .subdomains import fetch_subdomains
14
+ from .techdetect import detect_technologies
15
+
16
+ __all__ = [
17
+ "cli",
18
+ "ReconCache",
19
+ "get_cache",
20
+ "Config",
21
+ "get_config",
22
+ "ReconForgeLogger",
23
+ "get_logger",
24
+ "scan_port",
25
+ "scan_ports",
26
+ "check_targets",
27
+ "fetch_subdomains",
28
+ "detect_technologies",
29
+ ]
reconforge/analyzer.py ADDED
@@ -0,0 +1,309 @@
1
+ """Advanced analysis and filtering utilities for ReconForge findings."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, List, Optional
6
+
7
+
8
+ class FindingsAnalyzer:
9
+ """Analyze and filter recon findings for insights."""
10
+
11
+ def __init__(self, findings: Dict[str, Any]):
12
+ """Initialize analyzer with findings."""
13
+ self.findings = findings
14
+ self.subdomains = findings.get("subdomains", [])
15
+ self.ports = findings.get("ports", {})
16
+ self.technologies = findings.get("technologies", {})
17
+
18
+ def get_open_ports_summary(self) -> Dict[str, Any]:
19
+ """Get summary of open ports across all hosts."""
20
+ open_ports = {}
21
+ port_count = {}
22
+
23
+ for host, results in self.ports.items():
24
+ for result in results:
25
+ if result.get("status") == "open":
26
+ port = result.get("port")
27
+ if port not in open_ports:
28
+ open_ports[port] = []
29
+ port_count[port] = 0
30
+ open_ports[port].append(host)
31
+ port_count[port] += 1
32
+
33
+ return {
34
+ "open_ports": open_ports,
35
+ "port_count": port_count,
36
+ "total_open": sum(port_count.values()),
37
+ "unique_ports": len(open_ports),
38
+ }
39
+
40
+ def get_technology_summary(self) -> Dict[str, Any]:
41
+ """Get summary of detected technologies."""
42
+ tech_count = {}
43
+ tech_hosts = {}
44
+
45
+ for url, result in self.technologies.items():
46
+ for tech in result.get("technologies", []):
47
+ if tech not in tech_count:
48
+ tech_count[tech] = 0
49
+ tech_hosts[tech] = []
50
+ tech_count[tech] += 1
51
+ tech_hosts[tech].append(url)
52
+
53
+ # Sort by frequency
54
+ sorted_techs = sorted(tech_count.items(), key=lambda x: x[1], reverse=True)
55
+
56
+ return {
57
+ "technology_count": dict(sorted_techs),
58
+ "technology_hosts": tech_hosts,
59
+ "unique_technologies": len(tech_count),
60
+ "most_common": sorted_techs[0][0] if sorted_techs else None,
61
+ }
62
+
63
+ def find_interesting_ports(self) -> List[Dict[str, Any]]:
64
+ """Find potentially interesting open ports."""
65
+ interesting = {
66
+ 80: "HTTP",
67
+ 443: "HTTPS",
68
+ 8080: "HTTP Alt",
69
+ 8443: "HTTPS Alt",
70
+ 3000: "Node.js",
71
+ 5000: "Flask/Dev",
72
+ 8000: "Django/Dev",
73
+ 9000: "SonarQube",
74
+ 27017: "MongoDB",
75
+ 6379: "Redis",
76
+ 5432: "PostgreSQL",
77
+ 3306: "MySQL",
78
+ 1433: "MSSQL",
79
+ 22: "SSH",
80
+ 21: "FTP",
81
+ 25: "SMTP",
82
+ 110: "POP3",
83
+ 143: "IMAP",
84
+ }
85
+
86
+ found = []
87
+ for host, results in self.ports.items():
88
+ for result in results:
89
+ if result.get("status") == "open":
90
+ port = result.get("port")
91
+ if port in interesting:
92
+ found.append({
93
+ "host": host,
94
+ "port": port,
95
+ "service": interesting[port],
96
+ "banner": result.get("banner", ""),
97
+ })
98
+
99
+ return sorted(found, key=lambda x: x["port"])
100
+
101
+ def find_security_headers(self) -> Dict[str, Any]:
102
+ """Analyze security headers across all URLs."""
103
+ security_headers = {
104
+ "Strict-Transport-Security": "HSTS",
105
+ "Content-Security-Policy": "CSP",
106
+ "X-Content-Type-Options": "X-Content-Type-Options",
107
+ "X-Frame-Options": "Clickjacking Protection",
108
+ "X-XSS-Protection": "XSS Protection",
109
+ "Referrer-Policy": "Referrer Policy",
110
+ "Permissions-Policy": "Permissions Policy",
111
+ }
112
+
113
+ found = {}
114
+ missing = {}
115
+
116
+ for url, result in self.technologies.items():
117
+ headers = result.get("headers", {})
118
+ for header, name in security_headers.items():
119
+ if header in headers:
120
+ if name not in found:
121
+ found[name] = []
122
+ found[name].append(url)
123
+ else:
124
+ if name not in missing:
125
+ missing[name] = []
126
+ missing[name].append(url)
127
+
128
+ return {
129
+ "found": found,
130
+ "missing": missing,
131
+ "coverage": len(found) / len(security_headers) if security_headers else 0,
132
+ }
133
+
134
+ def find_potential_vulnerabilities(self) -> List[Dict[str, Any]]:
135
+ """Find potential vulnerability indicators."""
136
+ vulnerabilities = []
137
+
138
+ # Check for outdated technologies
139
+ outdated_patterns = {
140
+ "Apache/2.2": "Outdated Apache",
141
+ "nginx/1.0": "Outdated nginx",
142
+ "PHP/5": "Outdated PHP",
143
+ "jQuery/1": "Outdated jQuery",
144
+ }
145
+
146
+ for url, result in self.technologies.items():
147
+ headers = result.get("headers", {})
148
+ server = headers.get("Server", "")
149
+
150
+ for pattern, vuln in outdated_patterns.items():
151
+ if pattern.lower() in server.lower():
152
+ vulnerabilities.append({
153
+ "url": url,
154
+ "type": "Outdated Software",
155
+ "finding": vuln,
156
+ "severity": "Medium",
157
+ })
158
+
159
+ # Check for missing security headers
160
+ security_check = self.find_security_headers()
161
+ for header, urls in security_check["missing"].items():
162
+ if urls:
163
+ vulnerabilities.append({
164
+ "url": urls[0] if urls else "Unknown",
165
+ "type": "Missing Security Header",
166
+ "finding": f"Missing {header}",
167
+ "severity": "Low",
168
+ "affected_urls": len(urls),
169
+ })
170
+
171
+ return vulnerabilities
172
+
173
+ def get_subdomain_statistics(self) -> Dict[str, Any]:
174
+ """Get statistics about discovered subdomains."""
175
+ subdomain_patterns = {}
176
+ for subdomain in self.subdomains:
177
+ parts = subdomain.split(".")
178
+ if len(parts) > 2:
179
+ prefix = parts[0]
180
+ if prefix not in subdomain_patterns:
181
+ subdomain_patterns[prefix] = 0
182
+ subdomain_patterns[prefix] += 1
183
+
184
+ return {
185
+ "total_subdomains": len(self.subdomains),
186
+ "unique_prefixes": len(subdomain_patterns),
187
+ "common_prefixes": sorted(
188
+ subdomain_patterns.items(), key=lambda x: x[1], reverse=True
189
+ )[:10],
190
+ }
191
+
192
+ def get_risk_assessment(self) -> Dict[str, Any]:
193
+ """Get overall risk assessment."""
194
+ score = 0
195
+ findings = []
196
+
197
+ # Check for open ports
198
+ open_summary = self.get_open_ports_summary()
199
+ if open_summary["total_open"] > 5:
200
+ score += 20
201
+ findings.append(f"Many open ports detected ({open_summary['total_open']})")
202
+
203
+ # Check for common vulnerable ports
204
+ vulnerable_ports = [3306, 27017, 6379, 5432, 1433]
205
+ for port in vulnerable_ports:
206
+ if port in open_summary["open_ports"]:
207
+ score += 15
208
+ findings.append(f"Potentially exposed database port: {port}")
209
+
210
+ # Check for missing security headers
211
+ security = self.find_security_headers()
212
+ if security["coverage"] < 0.5:
213
+ score += 25
214
+ findings.append("Missing multiple security headers")
215
+
216
+ # Check for outdated software
217
+ vulns = self.find_potential_vulnerabilities()
218
+ if vulns:
219
+ score += 10 * len(vulns)
220
+ findings.append(f"Found {len(vulns)} potential vulnerability indicators")
221
+
222
+ # Normalize score to 0-100
223
+ score = min(score, 100)
224
+
225
+ risk_level = "Low" if score < 30 else "Medium" if score < 70 else "High"
226
+
227
+ return {
228
+ "risk_score": score,
229
+ "risk_level": risk_level,
230
+ "findings": findings,
231
+ "recommendations": self._get_recommendations(risk_level),
232
+ }
233
+
234
+ def _get_recommendations(self, risk_level: str) -> List[str]:
235
+ """Get recommendations based on risk level."""
236
+ recommendations = []
237
+
238
+ if risk_level == "High":
239
+ recommendations.extend([
240
+ "Conduct thorough security assessment",
241
+ "Review exposed services and ports",
242
+ "Implement security headers",
243
+ "Update outdated software",
244
+ "Consider WAF deployment",
245
+ ])
246
+ elif risk_level == "Medium":
247
+ recommendations.extend([
248
+ "Review security configuration",
249
+ "Add missing security headers",
250
+ "Update software to latest versions",
251
+ "Implement additional monitoring",
252
+ ])
253
+ else:
254
+ recommendations.extend([
255
+ "Continue security monitoring",
256
+ "Keep software updated",
257
+ "Maintain security headers",
258
+ ])
259
+
260
+ return recommendations
261
+
262
+ def generate_summary_report(self) -> str:
263
+ """Generate a text summary of findings."""
264
+ report = []
265
+ report.append("=" * 60)
266
+ report.append("RECONFORGE ANALYSIS SUMMARY")
267
+ report.append("=" * 60)
268
+ report.append("")
269
+
270
+ # Subdomain stats
271
+ subdomain_stats = self.get_subdomain_statistics()
272
+ report.append(f"Subdomains: {subdomain_stats['total_subdomains']}")
273
+ report.append(f"Unique Prefixes: {subdomain_stats['unique_prefixes']}")
274
+ report.append("")
275
+
276
+ # Open ports
277
+ port_summary = self.get_open_ports_summary()
278
+ report.append(f"Open Ports: {port_summary['total_open']}")
279
+ report.append(f"Unique Ports: {port_summary['unique_ports']}")
280
+ report.append("")
281
+
282
+ # Technologies
283
+ tech_summary = self.get_technology_summary()
284
+ report.append(f"Technologies Detected: {tech_summary['unique_technologies']}")
285
+ if tech_summary["most_common"]:
286
+ report.append(f"Most Common: {tech_summary['most_common']}")
287
+ report.append("")
288
+
289
+ # Risk assessment
290
+ risk = self.get_risk_assessment()
291
+ report.append(f"Risk Score: {risk['risk_score']}/100")
292
+ report.append(f"Risk Level: {risk['risk_level']}")
293
+ report.append("")
294
+
295
+ if risk["findings"]:
296
+ report.append("Key Findings:")
297
+ for finding in risk["findings"]:
298
+ report.append(f" - {finding}")
299
+ report.append("")
300
+
301
+ if risk["recommendations"]:
302
+ report.append("Recommendations:")
303
+ for rec in risk["recommendations"]:
304
+ report.append(f" - {rec}")
305
+
306
+ report.append("")
307
+ report.append("=" * 60)
308
+
309
+ return "\n".join(report)
reconforge/api.py ADDED
@@ -0,0 +1,196 @@
1
+ """Programmatic API for ReconForge - use ReconForge in your Python code."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ from .cache import get_cache
8
+ from .config import get_config
9
+ from .logger import get_logger
10
+ from .portscan import scan_ports
11
+ from .report import generate_report as _generate_report
12
+ from .scopecheck import check_targets
13
+ from .subdomains import fetch_subdomains
14
+ from .techdetect import detect_technologies
15
+
16
+
17
+ class ReconForgeAPI:
18
+ """Main API class for ReconForge."""
19
+
20
+ def __init__(self, use_cache: bool = True, log_file: Optional[str] = None):
21
+ """Initialize ReconForge API.
22
+
23
+ Args:
24
+ use_cache: Enable result caching
25
+ log_file: Optional log file path
26
+ """
27
+ self.cache = get_cache() if use_cache else None
28
+ self.config = get_config()
29
+ self.logger = get_logger(log_file=log_file)
30
+
31
+ def discover_subdomains(self, domain: str, timeout: Optional[float] = None) -> Dict[str, Any]:
32
+ """Discover subdomains for a domain.
33
+
34
+ Args:
35
+ domain: Target domain
36
+ timeout: Request timeout (uses config default if None)
37
+
38
+ Returns:
39
+ Dictionary with subdomains and errors
40
+ """
41
+ timeout = timeout or self.config.get("timeout", 10.0)
42
+
43
+ # Check cache
44
+ if self.cache:
45
+ cache_key = f"subdomains:{domain}"
46
+ cached = self.cache.get(cache_key)
47
+ if cached:
48
+ self.logger.info(f"Returning cached subdomains for {domain}")
49
+ return cached
50
+
51
+ self.logger.info(f"Discovering subdomains for {domain}")
52
+ subdomains, errors = fetch_subdomains(domain, timeout=timeout)
53
+
54
+ result = {
55
+ "domain": domain,
56
+ "subdomains": subdomains,
57
+ "errors": errors,
58
+ "count": len(subdomains),
59
+ }
60
+
61
+ # Cache result
62
+ if self.cache:
63
+ self.cache.set(cache_key, result)
64
+
65
+ return result
66
+
67
+ def scan_ports(
68
+ self,
69
+ host: str,
70
+ ports: Optional[List[int]] = None,
71
+ timeout: Optional[float] = None,
72
+ concurrent: Optional[bool] = None,
73
+ ) -> Dict[str, Any]:
74
+ """Scan ports on a host.
75
+
76
+ Args:
77
+ host: Target host
78
+ ports: List of ports to scan (uses defaults if None)
79
+ timeout: Socket timeout (uses config default if None)
80
+ concurrent: Use concurrent scanning (uses config default if None)
81
+
82
+ Returns:
83
+ Dictionary with scan results
84
+ """
85
+ timeout = timeout or self.config.get("port_scan_timeout", 2.0)
86
+ concurrent = concurrent if concurrent is not None else self.config.get("concurrent_scanning", True)
87
+
88
+ self.logger.info(f"Scanning ports on {host}")
89
+ result = scan_ports(host, ports=ports, timeout=timeout, concurrent=concurrent)
90
+
91
+ return result
92
+
93
+ def detect_technologies(self, url: str, timeout: Optional[float] = None) -> Dict[str, Any]:
94
+ """Detect technologies on a URL.
95
+
96
+ Args:
97
+ url: Target URL
98
+ timeout: Request timeout (uses config default if None)
99
+
100
+ Returns:
101
+ Dictionary with detected technologies
102
+ """
103
+ timeout = timeout or self.config.get("timeout", 10.0)
104
+
105
+ self.logger.info(f"Detecting technologies on {url}")
106
+ result = detect_technologies(url, timeout=timeout)
107
+
108
+ return result
109
+
110
+ def check_scope(self, targets_file: str, scope_file: str) -> Dict[str, Any]:
111
+ """Check if targets are in scope.
112
+
113
+ Args:
114
+ targets_file: Path to targets file
115
+ scope_file: Path to scope file
116
+
117
+ Returns:
118
+ Dictionary with in-scope and out-of-scope targets
119
+ """
120
+ self.logger.info(f"Checking scope for targets in {targets_file}")
121
+ result = check_targets(targets_file, scope_file)
122
+
123
+ return result
124
+
125
+ def generate_report(
126
+ self,
127
+ domain: str,
128
+ output: str,
129
+ timeout: Optional[float] = None,
130
+ max_hosts: Optional[int] = None,
131
+ concurrent: Optional[bool] = None,
132
+ ) -> Dict[str, Any]:
133
+ """Generate a comprehensive recon report.
134
+
135
+ Args:
136
+ domain: Target domain
137
+ output: Output file path
138
+ timeout: Request timeout (uses config default if None)
139
+ max_hosts: Maximum hosts to scan
140
+ concurrent: Use concurrent scanning (uses config default if None)
141
+
142
+ Returns:
143
+ Dictionary with report generation results
144
+ """
145
+ timeout = timeout or self.config.get("timeout", 10.0)
146
+ concurrent = concurrent if concurrent is not None else self.config.get("concurrent_scanning", True)
147
+
148
+ self.logger.info(f"Generating report for {domain}")
149
+ result = _generate_report(domain, output, timeout=timeout, max_hosts=max_hosts, concurrent=concurrent)
150
+
151
+ return result
152
+
153
+ def get_cache_stats(self) -> Dict[str, Any]:
154
+ """Get cache statistics."""
155
+ if not self.cache:
156
+ return {"cache_enabled": False}
157
+
158
+ return self.cache.get_stats()
159
+
160
+ def clear_cache(self) -> None:
161
+ """Clear all cached results."""
162
+ if self.cache:
163
+ self.cache.clear()
164
+ self.logger.info("Cache cleared")
165
+
166
+ def cleanup_expired_cache(self) -> int:
167
+ """Remove expired cache entries.
168
+
169
+ Returns:
170
+ Number of deleted entries
171
+ """
172
+ if not self.cache:
173
+ return 0
174
+
175
+ count = self.cache.cleanup_expired()
176
+ self.logger.info(f"Cleaned up {count} expired cache entries")
177
+ return count
178
+
179
+
180
+ # Convenience functions for quick access
181
+ def discover_subdomains(domain: str) -> Dict[str, Any]:
182
+ """Quick function to discover subdomains."""
183
+ api = ReconForgeAPI()
184
+ return api.discover_subdomains(domain)
185
+
186
+
187
+ def scan_ports_quick(host: str) -> Dict[str, Any]:
188
+ """Quick function to scan ports."""
189
+ api = ReconForgeAPI()
190
+ return api.scan_ports(host)
191
+
192
+
193
+ def detect_technologies_quick(url: str) -> Dict[str, Any]:
194
+ """Quick function to detect technologies."""
195
+ api = ReconForgeAPI()
196
+ return api.detect_technologies(url)