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.
- detecti/__init__.py +0 -0
- detecti/cli.py +649 -0
- detecti/config.py +188 -0
- detecti/core/__init__.py +1 -0
- detecti/core/database/__init__.py +5 -0
- detecti/core/database/config_db.py +73 -0
- detecti/core/database/schema.py +136 -0
- detecti/core/database/storage.py +1388 -0
- detecti/core/engine.py +1032 -0
- detecti/core/models.py +278 -0
- detecti/data/config.sqlite +0 -0
- detecti/data/dbs/.gitkeep +2 -0
- detecti/data/dbs/example.com.sqlite +0 -0
- detecti/modules/__init__.py +29 -0
- detecti/modules/base.py +57 -0
- detecti/modules/censys.py +813 -0
- detecti/modules/crtsh.py +98 -0
- detecti/modules/exploitdb.py +138 -0
- detecti/modules/masscan.py +561 -0
- detecti/modules/nuclei.py +449 -0
- detecti/modules/nvd.py +300 -0
- detecti/modules/reverse_whois.py +225 -0
- detecti/modules/shodan.py +412 -0
- detecti/reporters/__init__.py +7 -0
- detecti/reporters/csv_reporter.py +74 -0
- detecti/reporters/html_reporter.py +356 -0
- detecti/reporters/json_reporter.py +26 -0
- detecti/reporters/markdown_reporter.py +203 -0
- detecti/utils/__init__.py +1 -0
- detecti/utils/http.py +294 -0
- detecti/utils/logger.py +378 -0
- detecti/utils/setup.py +453 -0
- detecti/web/__init__.py +6 -0
- detecti/web/api/__init__.py +1 -0
- detecti/web/api/auth.py +109 -0
- detecti/web/api/graph_builder.py +901 -0
- detecti/web/api/routes.py +1602 -0
- detecti/web/process_manager.py +283 -0
- detecti/web/server.py +183 -0
- detecti/web/static/android-chrome-192x192.png +0 -0
- detecti/web/static/android-chrome-512x512.png +0 -0
- detecti/web/static/apple-touch-icon.png +0 -0
- detecti/web/static/css/__init__.py +1 -0
- detecti/web/static/css/dashboard.css +3802 -0
- detecti/web/static/favicon-16x16.png +0 -0
- detecti/web/static/favicon-32x32.png +0 -0
- detecti/web/static/favicon.ico +0 -0
- detecti/web/static/img/DetecTI_Security_Logo.png +0 -0
- detecti/web/static/img/detecti-ico.png +0 -0
- detecti/web/static/index.html +677 -0
- detecti/web/static/js/__init__.py +1 -0
- detecti/web/static/js/api.js +177 -0
- detecti/web/static/js/cytoscape-cose-bilkent.js +458 -0
- detecti/web/static/js/cytoscape-dagre.js +397 -0
- detecti/web/static/js/cytoscape.min.js +31 -0
- detecti/web/static/js/dagre.min.js +3809 -0
- detecti/web/static/js/graph.js +7439 -0
- detecti/web/static/js/lucide.min.js +12 -0
- detecti/web/static/login.html +290 -0
- detecti/web/static/site.webmanifest +1 -0
- detecti_cli-2.0.0.dist-info/METADATA +554 -0
- detecti_cli-2.0.0.dist-info/RECORD +64 -0
- detecti_cli-2.0.0.dist-info/WHEEL +4 -0
- detecti_cli-2.0.0.dist-info/entry_points.txt +3 -0
detecti/config.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Configuration settings for DetecTI-CLI using Pydantic Settings."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from pydantic import Field, model_validator
|
|
9
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Define the global base directory for DetecTI-CLI data
|
|
13
|
+
custom_path = os.getenv("DETECTI_HOME", str(Path.home() / ".detecti"))
|
|
14
|
+
DETECTI_HOME = Path(custom_path)
|
|
15
|
+
DETECTI_HOME.mkdir(parents=True, exist_ok=True)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def is_placeholder_key(val: Optional[str]) -> bool:
|
|
19
|
+
"""Check if a string represents an unconfigured placeholder or template value."""
|
|
20
|
+
if not val or not isinstance(val, str):
|
|
21
|
+
return True
|
|
22
|
+
cleaned = val.strip().lower()
|
|
23
|
+
if not cleaned:
|
|
24
|
+
return True
|
|
25
|
+
if cleaned in ("none", "null", "undefined", "dummy", "xxx", "placeholder", "changeme", "example"):
|
|
26
|
+
return True
|
|
27
|
+
placeholder_prefixes_or_suffixes = (
|
|
28
|
+
"insert_your_",
|
|
29
|
+
"your_api",
|
|
30
|
+
"your_token",
|
|
31
|
+
"your_key",
|
|
32
|
+
"seu_",
|
|
33
|
+
"sua_",
|
|
34
|
+
"_aqui",
|
|
35
|
+
"_here",
|
|
36
|
+
"token_aqui",
|
|
37
|
+
"chave_aqui",
|
|
38
|
+
"api_key_here",
|
|
39
|
+
"changeme",
|
|
40
|
+
"<insert",
|
|
41
|
+
)
|
|
42
|
+
if any(p in cleaned for p in placeholder_prefixes_or_suffixes):
|
|
43
|
+
return True
|
|
44
|
+
if cleaned.startswith("<") and cleaned.endswith(">"):
|
|
45
|
+
return True
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def sanitize_api_key(val: Optional[str]) -> Optional[str]:
|
|
50
|
+
"""Return stripped string if valid and not a placeholder, else None."""
|
|
51
|
+
if not val or is_placeholder_key(val):
|
|
52
|
+
return None
|
|
53
|
+
return val.strip()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _find_legacy_api_key() -> Optional[str]:
|
|
57
|
+
"""Look for legacy API.txt file in working directory or package root."""
|
|
58
|
+
candidate_paths = [
|
|
59
|
+
DETECTI_HOME / "API.txt",
|
|
60
|
+
Path.cwd() / "API.txt",
|
|
61
|
+
Path(__file__).resolve().parent / "API.txt",
|
|
62
|
+
Path(__file__).resolve().parent.parent / "API.txt",
|
|
63
|
+
]
|
|
64
|
+
for path in candidate_paths:
|
|
65
|
+
if path.is_file():
|
|
66
|
+
try:
|
|
67
|
+
content = path.read_text(encoding="utf-8").strip()
|
|
68
|
+
sanitized = sanitize_api_key(content)
|
|
69
|
+
if sanitized:
|
|
70
|
+
return sanitized
|
|
71
|
+
except Exception:
|
|
72
|
+
pass
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Settings(BaseSettings):
|
|
77
|
+
"""DetecTI Application Settings."""
|
|
78
|
+
|
|
79
|
+
model_config = SettingsConfigDict(
|
|
80
|
+
env_file=(str(DETECTI_HOME / ".env"), ".env", "detecti-cli/.env", "threattrack/.env"),
|
|
81
|
+
env_file_encoding="utf-8",
|
|
82
|
+
extra="ignore",
|
|
83
|
+
env_prefix="DETECTI_",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# API Keys (can also be read from direct standard env vars like SHODAN_API_KEY)
|
|
87
|
+
shodan_api_key: Optional[str] = Field(
|
|
88
|
+
default=None,
|
|
89
|
+
validation_alias="SHODAN_API_KEY",
|
|
90
|
+
description="Shodan.io API key",
|
|
91
|
+
)
|
|
92
|
+
nvd_api_key: Optional[str] = Field(
|
|
93
|
+
default=None,
|
|
94
|
+
validation_alias="NVD_API_KEY",
|
|
95
|
+
description="National Vulnerability Database API key (optional, allows faster queries)",
|
|
96
|
+
)
|
|
97
|
+
whoisfreaks_api_key: Optional[str] = Field(
|
|
98
|
+
default=None,
|
|
99
|
+
validation_alias="WHOISFREAKS_API_KEY",
|
|
100
|
+
description="WhoisFreaks API key for reverse WHOIS",
|
|
101
|
+
)
|
|
102
|
+
github_token: Optional[str] = Field(
|
|
103
|
+
default=None,
|
|
104
|
+
validation_alias="GITHUB_TOKEN",
|
|
105
|
+
description="GitHub personal access token for PoC queries",
|
|
106
|
+
)
|
|
107
|
+
censys_pat_token: Optional[str] = Field(
|
|
108
|
+
default=None,
|
|
109
|
+
validation_alias="CENSYS_PAT_TOKEN",
|
|
110
|
+
description="Censys Platform API v3 Personal Access Token (PAT)",
|
|
111
|
+
)
|
|
112
|
+
censys_org_id: Optional[str] = Field(
|
|
113
|
+
default=None,
|
|
114
|
+
validation_alias="CENSYS_ORG_ID",
|
|
115
|
+
description="Censys Platform API v3 Organization ID (optional)",
|
|
116
|
+
)
|
|
117
|
+
censys_api_id: Optional[str] = Field(
|
|
118
|
+
default=None,
|
|
119
|
+
validation_alias="CENSYS_API_ID",
|
|
120
|
+
description="Legacy Censys Search API ID (fallback)",
|
|
121
|
+
)
|
|
122
|
+
censys_api_secret: Optional[str] = Field(
|
|
123
|
+
default=None,
|
|
124
|
+
validation_alias="CENSYS_API_SECRET",
|
|
125
|
+
description="Legacy Censys Search API Secret (fallback)",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# HTTP Client Configuration
|
|
129
|
+
http_timeout: float = Field(default=15.0, description="HTTP timeout in seconds")
|
|
130
|
+
http_max_retries: int = Field(default=3, description="Max HTTP retries for failed requests")
|
|
131
|
+
http_backoff_factor: float = Field(default=0.5, description="Exponential backoff factor")
|
|
132
|
+
http_concurrency_limit: int = Field(default=10, description="Max concurrent async requests")
|
|
133
|
+
user_agent: str = Field(
|
|
134
|
+
default="DetecTI-CLI/2.0 (+https://github.com/detectisec/DetecTI-CLI)",
|
|
135
|
+
description="HTTP User-Agent header",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Rate Limiting Delays
|
|
139
|
+
nvd_delay_without_key: float = Field(default=6.0, description="Rate limit delay in seconds without NVD key")
|
|
140
|
+
nvd_delay_with_key: float = Field(default=0.6, description="Rate limit delay in seconds with NVD key")
|
|
141
|
+
hackertarget_delay: float = Field(default=1.0, description="Delay between HackerTarget free requests")
|
|
142
|
+
shodan_delay: float = Field(default=1.05, description="Rate limit delay in seconds for Shodan REST API (1 request/s limit)")
|
|
143
|
+
|
|
144
|
+
# Threat Intelligence Endpoint URLs
|
|
145
|
+
nvd_api_url: str = "https://services.nvd.nist.gov/rest/json/cves/2.0"
|
|
146
|
+
epss_api_url: str = "https://api.first.org/data/v1/epss"
|
|
147
|
+
cisa_kev_url: str = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
|
|
148
|
+
crtsh_api_url: str = "https://crt.sh"
|
|
149
|
+
github_poc_api_url: str = "https://poc-in-github.motikan2010.net/api/v1"
|
|
150
|
+
exploit_db_base_url: str = "https://www.exploit-db.com/exploits"
|
|
151
|
+
hackertarget_reverse_ip_url: str = "https://api.hackertarget.com/reverseiplookup"
|
|
152
|
+
hackertarget_whois_url: str = "https://api.hackertarget.com/whois"
|
|
153
|
+
whoisfreaks_reverse_whois_url: str = "https://api.whoisfreaks.com/v1.0/reversewhois"
|
|
154
|
+
censys_platform_api_url: str = "https://api.platform.censys.io/v3"
|
|
155
|
+
censys_hosts_api_url: str = "https://api.platform.censys.io/v3/global"
|
|
156
|
+
|
|
157
|
+
@model_validator(mode="after")
|
|
158
|
+
def populate_fallback_keys(self) -> Settings:
|
|
159
|
+
"""Fallback to direct environment variables or API.txt if not set, filtering out placeholders."""
|
|
160
|
+
raw_shodan = self.shodan_api_key or os.getenv("SHODAN_API_KEY") or _find_legacy_api_key()
|
|
161
|
+
self.shodan_api_key = sanitize_api_key(raw_shodan)
|
|
162
|
+
|
|
163
|
+
raw_nvd = self.nvd_api_key or os.getenv("NVD_API_KEY")
|
|
164
|
+
self.nvd_api_key = sanitize_api_key(raw_nvd)
|
|
165
|
+
|
|
166
|
+
raw_whois = self.whoisfreaks_api_key or os.getenv("WHOISFREAKS_API_KEY")
|
|
167
|
+
self.whoisfreaks_api_key = sanitize_api_key(raw_whois)
|
|
168
|
+
|
|
169
|
+
raw_github = self.github_token or os.getenv("GITHUB_TOKEN")
|
|
170
|
+
self.github_token = sanitize_api_key(raw_github)
|
|
171
|
+
|
|
172
|
+
raw_censys_pat = self.censys_pat_token or os.getenv("CENSYS_PAT_TOKEN")
|
|
173
|
+
self.censys_pat_token = sanitize_api_key(raw_censys_pat)
|
|
174
|
+
|
|
175
|
+
raw_censys_org = self.censys_org_id or os.getenv("CENSYS_ORG_ID")
|
|
176
|
+
self.censys_org_id = sanitize_api_key(raw_censys_org)
|
|
177
|
+
|
|
178
|
+
raw_censys_id = self.censys_api_id or os.getenv("CENSYS_API_ID")
|
|
179
|
+
self.censys_api_id = sanitize_api_key(raw_censys_id)
|
|
180
|
+
|
|
181
|
+
raw_censys_secret = self.censys_api_secret or os.getenv("CENSYS_API_SECRET")
|
|
182
|
+
self.censys_api_secret = sanitize_api_key(raw_censys_secret)
|
|
183
|
+
|
|
184
|
+
return self
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# Global singleton settings instance
|
|
188
|
+
settings = Settings()
|
detecti/core/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core module package for ThreatTrack."""
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sqlite3
|
|
3
|
+
import hashlib
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
# Suppress passlib bcrypt warning
|
|
7
|
+
try:
|
|
8
|
+
import bcrypt
|
|
9
|
+
if not hasattr(bcrypt, "__about__"):
|
|
10
|
+
class AboutMock:
|
|
11
|
+
__version__ = bcrypt.__version__
|
|
12
|
+
bcrypt.__about__ = AboutMock()
|
|
13
|
+
except ImportError:
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
from passlib.context import CryptContext
|
|
17
|
+
from pydantic_settings import BaseSettings
|
|
18
|
+
|
|
19
|
+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
20
|
+
|
|
21
|
+
def get_password_hash(password: str) -> str:
|
|
22
|
+
return pwd_context.hash(password)
|
|
23
|
+
|
|
24
|
+
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
|
25
|
+
return pwd_context.verify(plain_password, hashed_password)
|
|
26
|
+
|
|
27
|
+
class ConfigDBManager:
|
|
28
|
+
def __init__(self, db_path: Path):
|
|
29
|
+
self.db_path = db_path
|
|
30
|
+
self.db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
31
|
+
self.init_db()
|
|
32
|
+
|
|
33
|
+
def init_db(self):
|
|
34
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
35
|
+
conn.execute('''
|
|
36
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
37
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
38
|
+
username TEXT UNIQUE NOT NULL,
|
|
39
|
+
password_hash TEXT NOT NULL,
|
|
40
|
+
role TEXT DEFAULT 'admin',
|
|
41
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
42
|
+
)
|
|
43
|
+
''')
|
|
44
|
+
conn.commit()
|
|
45
|
+
|
|
46
|
+
def create_user(self, username: str, password_hash: str, role: str = 'admin'):
|
|
47
|
+
try:
|
|
48
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
49
|
+
conn.execute('''
|
|
50
|
+
INSERT INTO users (username, password_hash, role)
|
|
51
|
+
VALUES (?, ?, ?)
|
|
52
|
+
''', (username, password_hash, role))
|
|
53
|
+
conn.commit()
|
|
54
|
+
return True
|
|
55
|
+
except sqlite3.IntegrityError:
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
def update_user_password(self, username: str, password_hash: str):
|
|
59
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
60
|
+
conn.execute('''
|
|
61
|
+
UPDATE users SET password_hash = ? WHERE username = ?
|
|
62
|
+
''', (password_hash, username))
|
|
63
|
+
conn.commit()
|
|
64
|
+
|
|
65
|
+
def get_user(self, username: str):
|
|
66
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
67
|
+
conn.row_factory = sqlite3.Row
|
|
68
|
+
cursor = conn.execute("SELECT * FROM users WHERE username = ?", (username,))
|
|
69
|
+
return cursor.fetchone()
|
|
70
|
+
|
|
71
|
+
def user_exists(self, username: str) -> bool:
|
|
72
|
+
return self.get_user(username) is not None
|
|
73
|
+
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""SQLite schema definition for DetecTI-CLI EASM database."""
|
|
2
|
+
|
|
3
|
+
SCHEMA_SQL = """
|
|
4
|
+
-- Target Domains
|
|
5
|
+
CREATE TABLE IF NOT EXISTS domains (
|
|
6
|
+
id TEXT PRIMARY KEY,
|
|
7
|
+
name TEXT UNIQUE NOT NULL,
|
|
8
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
-- Subdomains
|
|
12
|
+
CREATE TABLE IF NOT EXISTS subdomains (
|
|
13
|
+
id TEXT PRIMARY KEY,
|
|
14
|
+
domain_id TEXT NOT NULL,
|
|
15
|
+
name TEXT NOT NULL,
|
|
16
|
+
status_code INTEGER,
|
|
17
|
+
cname TEXT,
|
|
18
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
19
|
+
FOREIGN KEY (domain_id) REFERENCES domains(id)
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
-- IP Addresses
|
|
23
|
+
CREATE TABLE IF NOT EXISTS ip_addresses (
|
|
24
|
+
id TEXT PRIMARY KEY,
|
|
25
|
+
ip TEXT UNIQUE NOT NULL,
|
|
26
|
+
asn TEXT,
|
|
27
|
+
org TEXT,
|
|
28
|
+
country TEXT,
|
|
29
|
+
city TEXT,
|
|
30
|
+
region_code TEXT,
|
|
31
|
+
postal_code TEXT,
|
|
32
|
+
latitude REAL,
|
|
33
|
+
longitude REAL,
|
|
34
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
-- Mapping Subdomain to IP (DNS Resolutions)
|
|
38
|
+
CREATE TABLE IF NOT EXISTS subdomain_ips (
|
|
39
|
+
subdomain_id TEXT NOT NULL,
|
|
40
|
+
ip_id TEXT NOT NULL,
|
|
41
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
42
|
+
PRIMARY KEY (subdomain_id, ip_id),
|
|
43
|
+
FOREIGN KEY (subdomain_id) REFERENCES subdomains(id),
|
|
44
|
+
FOREIGN KEY (ip_id) REFERENCES ip_addresses(id)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
-- Exposed Ports & Services
|
|
48
|
+
CREATE TABLE IF NOT EXISTS services (
|
|
49
|
+
id TEXT PRIMARY KEY,
|
|
50
|
+
ip_id TEXT NOT NULL,
|
|
51
|
+
port INTEGER NOT NULL,
|
|
52
|
+
protocol TEXT DEFAULT 'tcp',
|
|
53
|
+
service_name TEXT,
|
|
54
|
+
product TEXT,
|
|
55
|
+
version TEXT,
|
|
56
|
+
banner TEXT,
|
|
57
|
+
url TEXT,
|
|
58
|
+
ssl BOOLEAN DEFAULT 0,
|
|
59
|
+
sources TEXT,
|
|
60
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
61
|
+
FOREIGN KEY (ip_id) REFERENCES ip_addresses(id)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
-- Vulnerabilities & EPSS/KEV Intelligence
|
|
65
|
+
CREATE TABLE IF NOT EXISTS vulnerabilities (
|
|
66
|
+
id TEXT PRIMARY KEY,
|
|
67
|
+
service_id TEXT,
|
|
68
|
+
ip_id TEXT,
|
|
69
|
+
cve_id TEXT NOT NULL,
|
|
70
|
+
severity TEXT,
|
|
71
|
+
cvss_score REAL,
|
|
72
|
+
cvss_version TEXT,
|
|
73
|
+
description TEXT,
|
|
74
|
+
cwe_id TEXT,
|
|
75
|
+
cwe_name TEXT,
|
|
76
|
+
epss_score REAL,
|
|
77
|
+
epss_percentile REAL,
|
|
78
|
+
is_cisa_kev BOOLEAN DEFAULT 0,
|
|
79
|
+
cisa_kev_data TEXT,
|
|
80
|
+
source TEXT,
|
|
81
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
82
|
+
FOREIGN KEY (service_id) REFERENCES services(id),
|
|
83
|
+
FOREIGN KEY (ip_id) REFERENCES ip_addresses(id)
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
-- Exploits and PoCs
|
|
87
|
+
CREATE TABLE IF NOT EXISTS exploits (
|
|
88
|
+
id TEXT PRIMARY KEY,
|
|
89
|
+
vulnerability_id TEXT NOT NULL,
|
|
90
|
+
title TEXT NOT NULL,
|
|
91
|
+
source TEXT NOT NULL,
|
|
92
|
+
url TEXT NOT NULL,
|
|
93
|
+
verified BOOLEAN DEFAULT 0,
|
|
94
|
+
author TEXT,
|
|
95
|
+
date TEXT,
|
|
96
|
+
exploit_type TEXT,
|
|
97
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
98
|
+
FOREIGN KEY (vulnerability_id) REFERENCES vulnerabilities(id)
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
-- Scan Results Metadata
|
|
102
|
+
CREATE TABLE IF NOT EXISTS scan_results (
|
|
103
|
+
id TEXT PRIMARY KEY,
|
|
104
|
+
target TEXT NOT NULL,
|
|
105
|
+
target_type TEXT NOT NULL,
|
|
106
|
+
started_at TIMESTAMP NOT NULL,
|
|
107
|
+
completed_at TIMESTAMP,
|
|
108
|
+
elapsed_seconds REAL,
|
|
109
|
+
modules_run TEXT,
|
|
110
|
+
total_findings INTEGER DEFAULT 0,
|
|
111
|
+
total_hosts INTEGER DEFAULT 0,
|
|
112
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
-- Scan Live Logs Persistence
|
|
116
|
+
CREATE TABLE IF NOT EXISTS scan_logs (
|
|
117
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
118
|
+
timestamp TEXT NOT NULL,
|
|
119
|
+
level TEXT NOT NULL,
|
|
120
|
+
message TEXT NOT NULL,
|
|
121
|
+
target TEXT,
|
|
122
|
+
input_target TEXT,
|
|
123
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
-- Create indexes for performance
|
|
127
|
+
CREATE INDEX IF NOT EXISTS idx_subdomains_domain_id ON subdomains(domain_id);
|
|
128
|
+
CREATE INDEX IF NOT EXISTS idx_services_ip_id ON services(ip_id);
|
|
129
|
+
CREATE INDEX IF NOT EXISTS idx_vulnerabilities_service_id ON vulnerabilities(service_id);
|
|
130
|
+
CREATE INDEX IF NOT EXISTS idx_vulnerabilities_ip_id ON vulnerabilities(ip_id);
|
|
131
|
+
CREATE INDEX IF NOT EXISTS idx_vulnerabilities_cve_id ON vulnerabilities(cve_id);
|
|
132
|
+
CREATE INDEX IF NOT EXISTS idx_exploits_vulnerability_id ON exploits(vulnerability_id);
|
|
133
|
+
CREATE INDEX IF NOT EXISTS idx_scan_results_target ON scan_results(target);
|
|
134
|
+
CREATE INDEX IF NOT EXISTS idx_scan_logs_target ON scan_logs(target);
|
|
135
|
+
CREATE INDEX IF NOT EXISTS idx_scan_logs_id ON scan_logs(id);
|
|
136
|
+
"""
|