detecti-cli 2.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.
Files changed (64) hide show
  1. detecti/__init__.py +0 -0
  2. detecti/cli.py +649 -0
  3. detecti/config.py +188 -0
  4. detecti/core/__init__.py +1 -0
  5. detecti/core/database/__init__.py +5 -0
  6. detecti/core/database/config_db.py +73 -0
  7. detecti/core/database/schema.py +136 -0
  8. detecti/core/database/storage.py +1388 -0
  9. detecti/core/engine.py +1032 -0
  10. detecti/core/models.py +278 -0
  11. detecti/data/config.sqlite +0 -0
  12. detecti/data/dbs/.gitkeep +2 -0
  13. detecti/data/dbs/example.com.sqlite +0 -0
  14. detecti/modules/__init__.py +29 -0
  15. detecti/modules/base.py +57 -0
  16. detecti/modules/censys.py +813 -0
  17. detecti/modules/crtsh.py +98 -0
  18. detecti/modules/exploitdb.py +138 -0
  19. detecti/modules/masscan.py +561 -0
  20. detecti/modules/nuclei.py +449 -0
  21. detecti/modules/nvd.py +300 -0
  22. detecti/modules/reverse_whois.py +225 -0
  23. detecti/modules/shodan.py +412 -0
  24. detecti/reporters/__init__.py +7 -0
  25. detecti/reporters/csv_reporter.py +74 -0
  26. detecti/reporters/html_reporter.py +356 -0
  27. detecti/reporters/json_reporter.py +26 -0
  28. detecti/reporters/markdown_reporter.py +203 -0
  29. detecti/utils/__init__.py +1 -0
  30. detecti/utils/http.py +294 -0
  31. detecti/utils/logger.py +378 -0
  32. detecti/utils/setup.py +453 -0
  33. detecti/web/__init__.py +6 -0
  34. detecti/web/api/__init__.py +1 -0
  35. detecti/web/api/auth.py +109 -0
  36. detecti/web/api/graph_builder.py +901 -0
  37. detecti/web/api/routes.py +1602 -0
  38. detecti/web/process_manager.py +283 -0
  39. detecti/web/server.py +183 -0
  40. detecti/web/static/android-chrome-192x192.png +0 -0
  41. detecti/web/static/android-chrome-512x512.png +0 -0
  42. detecti/web/static/apple-touch-icon.png +0 -0
  43. detecti/web/static/css/__init__.py +1 -0
  44. detecti/web/static/css/dashboard.css +3802 -0
  45. detecti/web/static/favicon-16x16.png +0 -0
  46. detecti/web/static/favicon-32x32.png +0 -0
  47. detecti/web/static/favicon.ico +0 -0
  48. detecti/web/static/img/DetecTI_Security_Logo.png +0 -0
  49. detecti/web/static/img/detecti-ico.png +0 -0
  50. detecti/web/static/index.html +677 -0
  51. detecti/web/static/js/__init__.py +1 -0
  52. detecti/web/static/js/api.js +177 -0
  53. detecti/web/static/js/cytoscape-cose-bilkent.js +458 -0
  54. detecti/web/static/js/cytoscape-dagre.js +397 -0
  55. detecti/web/static/js/cytoscape.min.js +31 -0
  56. detecti/web/static/js/dagre.min.js +3809 -0
  57. detecti/web/static/js/graph.js +7439 -0
  58. detecti/web/static/js/lucide.min.js +12 -0
  59. detecti/web/static/login.html +290 -0
  60. detecti/web/static/site.webmanifest +1 -0
  61. detecti_cli-2.0.0.dist-info/METADATA +554 -0
  62. detecti_cli-2.0.0.dist-info/RECORD +64 -0
  63. detecti_cli-2.0.0.dist-info/WHEEL +4 -0
  64. detecti_cli-2.0.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,98 @@
1
+ """Certificate Transparency (crt.sh) Subdomain Enumeration Module."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import re
7
+ from typing import Any, Dict, List, Optional, Set
8
+ from detecti.config import settings
9
+ from detecti.core.models import Finding, FindingType
10
+ from detecti.modules.base import BaseModule
11
+
12
+ logger = logging.getLogger("detecti.crtsh")
13
+
14
+
15
+ class CrtshModule(BaseModule):
16
+ """Enumerates subdomains using public Certificate Transparency logs via crt.sh."""
17
+
18
+ name: str = "crtsh"
19
+ description: str = "Certificate Transparency logs subdomain discovery"
20
+ category: str = "recon"
21
+
22
+ # Regex to validate valid domain/subdomain formats
23
+ DOMAIN_REGEX = re.compile(
24
+ r"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$"
25
+ )
26
+
27
+ async def run(
28
+ self,
29
+ target: str,
30
+ context: Optional[Dict[str, Any]] = None,
31
+ ) -> List[Finding]:
32
+ """Query crt.sh for domain certificates and extract unique subdomains."""
33
+ # Clean target and extract registered root domain
34
+ domain = target.strip().lower()
35
+ if domain.startswith("http://") or domain.startswith("https://"):
36
+ domain = domain.split("//")[1].split("/")[0]
37
+ if ":" in domain:
38
+ domain = domain.split(":")[0]
39
+ if "/" in domain:
40
+ domain = domain.split("/")[0]
41
+
42
+ import tldextract
43
+ ext = tldextract.extract(domain)
44
+ search_domain = ext.registered_domain if (ext.domain and ext.suffix) else domain
45
+
46
+ # crt.sh query format
47
+ url = f"{settings.crtsh_api_url}/"
48
+ params = {"q": f"%.{search_domain}", "output": "json"}
49
+
50
+ findings: List[Finding] = []
51
+ discovered_subdomains: Set[str] = set()
52
+
53
+ try:
54
+ data = await self.http_client.get_json(url=url, params=params, timeout=20.0)
55
+ if not data or not isinstance(data, list):
56
+ logger.debug(f"No certificate data returned from crt.sh for {domain}")
57
+ return findings
58
+
59
+ for entry in data:
60
+ name_value = entry.get("name_value", "")
61
+ if not name_value:
62
+ continue
63
+
64
+ # An entry may contain multiple names separated by newlines
65
+ names = name_value.split("\n")
66
+ for name in names:
67
+ clean_name = name.strip().lower()
68
+ # Strip leading wildcard *.
69
+ if clean_name.startswith("*."):
70
+ clean_name = clean_name[2:]
71
+
72
+ if (
73
+ clean_name
74
+ and clean_name.endswith(domain)
75
+ and self.DOMAIN_REGEX.match(clean_name)
76
+ ):
77
+ discovered_subdomains.add(clean_name)
78
+
79
+ for sub in sorted(discovered_subdomains):
80
+ findings.append(
81
+ Finding(
82
+ type=FindingType.SUBDOMAIN,
83
+ target=domain,
84
+ value=sub,
85
+ source="crt.sh",
86
+ metadata={
87
+ "parent_domain": domain,
88
+ "is_apex": sub == domain,
89
+ },
90
+ )
91
+ )
92
+
93
+ logger.info(f"crt.sh discovered {len(findings)} unique subdomains for {domain}")
94
+
95
+ except Exception as exc:
96
+ logger.warning(f"Error querying crt.sh for {domain}: {exc}")
97
+
98
+ return findings
@@ -0,0 +1,138 @@
1
+ """ExploitDB & GitHub Proof of Concept (PoC) Intelligence Module."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ from typing import Any, Dict, List, Optional, Set
8
+ import cve_searchsploit as cs
9
+ from detecti.config import settings
10
+ from detecti.core.models import ExploitData, Finding, FindingType
11
+ from detecti.modules.base import BaseModule
12
+
13
+ logger = logging.getLogger("detecti.exploitdb")
14
+
15
+
16
+ class ExploitDBModule(BaseModule):
17
+ """Searches for public exploits and GitHub Proof-of-Concepts (PoCs) for CVEs."""
18
+
19
+ name: str = "exploitdb"
20
+ description: str = "ExploitDB and GitHub PoC exploit intelligence collector"
21
+ category: str = "exploit"
22
+
23
+ @staticmethod
24
+ def update_database() -> None:
25
+ """Update local ExploitDB searchsploit mapping database."""
26
+ logger.info("Updating local ExploitDB/SearchSploit database...")
27
+ cs.update_db()
28
+
29
+ async def run(
30
+ self,
31
+ target: str,
32
+ context: Optional[Dict[str, Any]] = None,
33
+ ) -> List[Finding]:
34
+ """Collect exploits and PoCs for targeted CVEs."""
35
+ target = target.strip()
36
+ cves_to_search: Set[str] = set()
37
+
38
+ if target.upper().startswith("CVE-"):
39
+ cves_to_search.add(target.upper())
40
+
41
+ if context and "cves" in context:
42
+ for c in context["cves"]:
43
+ if isinstance(c, str) and c.upper().startswith("CVE-"):
44
+ cves_to_search.add(c.upper())
45
+
46
+ if not cves_to_search:
47
+ logger.debug("No CVEs provided to ExploitDB module.")
48
+ return []
49
+
50
+ findings: List[Finding] = []
51
+ for cve_id in sorted(cves_to_search):
52
+ exploits = await self.get_exploits_for_cve(cve_id)
53
+ for exp in exploits:
54
+ findings.append(
55
+ Finding(
56
+ type=FindingType.EXPLOIT,
57
+ target=target,
58
+ value=f"{cve_id} -> {exp.title}",
59
+ source=exp.source,
60
+ exploit=exp,
61
+ metadata={"cve_id": cve_id, "url": exp.url},
62
+ )
63
+ )
64
+
65
+ return findings
66
+
67
+ async def get_exploits_for_cve(self, cve_id: str) -> List[ExploitData]:
68
+ """Search ExploitDB (via cve_searchsploit) and GitHub PoC API concurrently."""
69
+ results: List[ExploitData] = []
70
+
71
+ # 1. Search ExploitDB
72
+ xdb_task = asyncio.to_thread(self._search_exploitdb, cve_id)
73
+ # 2. Search GitHub PoCs
74
+ git_task = self._search_github_pocs(cve_id)
75
+
76
+ xdb_results, git_results = await asyncio.gather(xdb_task, git_task, return_exceptions=True)
77
+
78
+ if isinstance(xdb_results, list):
79
+ results.extend(xdb_results)
80
+ elif isinstance(xdb_results, Exception):
81
+ logger.debug(f"Error querying ExploitDB for {cve_id}: {xdb_results}")
82
+
83
+ if isinstance(git_results, list):
84
+ results.extend(git_results)
85
+ elif isinstance(git_results, Exception):
86
+ logger.debug(f"Error querying GitHub PoCs for {cve_id}: {git_results}")
87
+
88
+ return results
89
+
90
+ def _search_exploitdb(self, cve_id: str) -> List[ExploitData]:
91
+ """Search local cve_searchsploit index for ExploitDB entries."""
92
+ exploits: List[ExploitData] = []
93
+ try:
94
+ edb_ids = cs.edbid_from_cve(cve_id)
95
+ if edb_ids:
96
+ for eid in edb_ids:
97
+ url = f"{settings.exploit_db_base_url}/{eid}"
98
+ exploits.append(
99
+ ExploitData(
100
+ title=f"ExploitDB EDB-ID {eid} for {cve_id}",
101
+ source="ExploitDB",
102
+ url=url,
103
+ verified=True,
104
+ exploit_type="Exploit",
105
+ )
106
+ )
107
+ except Exception as exc:
108
+ logger.debug(f"cve_searchsploit lookup failed for {cve_id}: {exc}")
109
+ return exploits
110
+
111
+ async def _search_github_pocs(self, cve_id: str) -> List[ExploitData]:
112
+ """Search PoC-in-GitHub community aggregator for public PoC repositories."""
113
+ exploits: List[ExploitData] = []
114
+ url = f"{settings.github_poc_api_url}/"
115
+ params = {"cve_id": cve_id}
116
+
117
+ try:
118
+ data = await self.http_client.get_json(url=url, params=params, timeout=12.0)
119
+ if data and isinstance(data, dict) and "pocs" in data and data["pocs"]:
120
+ for item in data["pocs"]:
121
+ repo_name = item.get("name") or item.get("html_url", "").split("/")[-1]
122
+ html_url = item.get("html_url")
123
+ owner = item.get("owner", {}).get("login") if isinstance(item.get("owner"), dict) else None
124
+ if html_url:
125
+ exploits.append(
126
+ ExploitData(
127
+ title=f"GitHub PoC ({repo_name})",
128
+ source="GitHub",
129
+ url=html_url,
130
+ verified=False,
131
+ author=owner,
132
+ exploit_type="Proof of Concept",
133
+ )
134
+ )
135
+ except Exception as exc:
136
+ logger.debug(f"GitHub PoC lookup failed for {cve_id}: {exc}")
137
+
138
+ return exploits