Full security platform with web dashboard, 16 Flask blueprints, 26 modules, autonomous AI agent, WebUSB hardware support, and Archon Android companion app. Includes Hash Toolkit, debug console, anti-stalkerware shield, Metasploit/RouterSploit integration, WireGuard VPN, OSINT reconnaissance, and multi-backend LLM support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1259 lines
49 KiB
Python
1259 lines
49 KiB
Python
"""
|
|
AUTARCH My System Module
|
|
Comprehensive system security audit with CVE detection and remediation
|
|
|
|
Performs full system audit, saves results, and offers LLM-assisted or manual fixes.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import subprocess
|
|
import socket
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
from typing import Optional, List, Dict, Any
|
|
|
|
# Module metadata
|
|
DESCRIPTION = "System audit with CVE detection & auto-fix"
|
|
AUTHOR = "darkHal"
|
|
VERSION = "1.0"
|
|
CATEGORY = "defense"
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
from core.banner import Colors, clear_screen, display_banner
|
|
from core.config import get_config
|
|
from core.cve import get_cve_db
|
|
|
|
|
|
class SecurityIssue:
|
|
"""Represents a security issue found during audit."""
|
|
|
|
SEVERITY_COLORS = {
|
|
'CRITICAL': Colors.RED,
|
|
'HIGH': Colors.RED,
|
|
'MEDIUM': Colors.YELLOW,
|
|
'LOW': Colors.CYAN,
|
|
'INFO': Colors.DIM,
|
|
}
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
description: str,
|
|
severity: str,
|
|
category: str,
|
|
fix_command: str = None,
|
|
fix_instructions: str = None,
|
|
cve_ids: List[str] = None
|
|
):
|
|
self.name = name
|
|
self.description = description
|
|
self.severity = severity.upper()
|
|
self.category = category
|
|
self.fix_command = fix_command
|
|
self.fix_instructions = fix_instructions
|
|
self.cve_ids = cve_ids or []
|
|
self.status = "open" # open, fixed, ignored
|
|
|
|
def to_dict(self) -> Dict:
|
|
return {
|
|
'name': self.name,
|
|
'description': self.description,
|
|
'severity': self.severity,
|
|
'category': self.category,
|
|
'fix_command': self.fix_command,
|
|
'fix_instructions': self.fix_instructions,
|
|
'cve_ids': self.cve_ids,
|
|
'status': self.status,
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict) -> 'SecurityIssue':
|
|
issue = cls(
|
|
name=data.get('name', ''),
|
|
description=data.get('description', ''),
|
|
severity=data.get('severity', 'MEDIUM'),
|
|
category=data.get('category', 'general'),
|
|
fix_command=data.get('fix_command'),
|
|
fix_instructions=data.get('fix_instructions'),
|
|
cve_ids=data.get('cve_ids', []),
|
|
)
|
|
issue.status = data.get('status', 'open')
|
|
return issue
|
|
|
|
|
|
class MySystem:
|
|
"""Comprehensive system security auditor."""
|
|
|
|
@staticmethod
|
|
def _system_inf_path():
|
|
from core.paths import get_app_dir
|
|
return get_app_dir() / "system.inf"
|
|
|
|
def __init__(self):
|
|
self.issues: List[SecurityIssue] = []
|
|
self.system_info: Dict = {}
|
|
self.audit_results: Dict = {}
|
|
self.security_score: int = 100
|
|
self.cve_db = get_cve_db()
|
|
self.llm = None
|
|
|
|
def print_status(self, message: str, status: str = "info"):
|
|
colors = {
|
|
"info": Colors.CYAN,
|
|
"success": Colors.GREEN,
|
|
"warning": Colors.YELLOW,
|
|
"error": Colors.RED
|
|
}
|
|
symbols = {"info": "*", "success": "+", "warning": "!", "error": "X"}
|
|
print(f"{colors.get(status, Colors.WHITE)}[{symbols.get(status, '*')}] {message}{Colors.RESET}")
|
|
|
|
def run_cmd(self, cmd: str, timeout: int = 10) -> tuple:
|
|
"""Run command and return (success, output)."""
|
|
try:
|
|
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
|
|
return result.returncode == 0, result.stdout.strip()
|
|
except:
|
|
return False, ""
|
|
|
|
def collect_system_info(self):
|
|
"""Collect comprehensive system information."""
|
|
self.print_status("Collecting system information...")
|
|
|
|
info = self.cve_db.get_system_info()
|
|
|
|
# Additional system info
|
|
success, output = self.run_cmd("hostname")
|
|
info['hostname'] = output if success else 'unknown'
|
|
|
|
success, output = self.run_cmd("uptime -p 2>/dev/null || uptime")
|
|
info['uptime'] = output if success else 'unknown'
|
|
|
|
success, output = self.run_cmd("whoami")
|
|
info['current_user'] = output if success else 'unknown'
|
|
|
|
success, output = self.run_cmd("cat /proc/meminfo 2>/dev/null | grep MemTotal | awk '{print $2}'")
|
|
if success and output:
|
|
info['memory_kb'] = int(output)
|
|
info['memory_gb'] = round(int(output) / 1024 / 1024, 1)
|
|
|
|
success, output = self.run_cmd("nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null")
|
|
info['cpu_cores'] = int(output) if success and output.isdigit() else 0
|
|
|
|
self.system_info = info
|
|
|
|
def add_issue(
|
|
self,
|
|
name: str,
|
|
description: str,
|
|
severity: str,
|
|
category: str,
|
|
fix_command: str = None,
|
|
fix_instructions: str = None,
|
|
score_penalty: int = 5
|
|
):
|
|
"""Add a security issue to the list."""
|
|
issue = SecurityIssue(
|
|
name=name,
|
|
description=description,
|
|
severity=severity,
|
|
category=category,
|
|
fix_command=fix_command,
|
|
fix_instructions=fix_instructions,
|
|
)
|
|
self.issues.append(issue)
|
|
|
|
# Adjust score based on severity
|
|
penalties = {'CRITICAL': 20, 'HIGH': 15, 'MEDIUM': 10, 'LOW': 5, 'INFO': 0}
|
|
self.security_score -= penalties.get(severity.upper(), score_penalty)
|
|
self.security_score = max(0, self.security_score)
|
|
|
|
# =========================================================================
|
|
# AUDIT CHECKS
|
|
# =========================================================================
|
|
|
|
def check_firewall(self):
|
|
"""Check firewall status."""
|
|
self.print_status("Checking firewall...")
|
|
|
|
# Check iptables
|
|
success, output = self.run_cmd("iptables -L -n 2>/dev/null | head -20")
|
|
if success and "Chain" in output:
|
|
rules = output.count("\n")
|
|
if rules > 5:
|
|
self.audit_results['firewall'] = {'status': 'enabled', 'type': 'iptables', 'rules': rules}
|
|
return
|
|
else:
|
|
self.add_issue(
|
|
"Firewall - Minimal Rules",
|
|
f"iptables has only {rules} rules configured",
|
|
"MEDIUM",
|
|
"network",
|
|
fix_instructions="Configure iptables with appropriate rules or use ufw/firewalld for easier management"
|
|
)
|
|
return
|
|
|
|
# Check ufw
|
|
success, output = self.run_cmd("ufw status 2>/dev/null")
|
|
if success and "active" in output.lower():
|
|
self.audit_results['firewall'] = {'status': 'enabled', 'type': 'ufw'}
|
|
return
|
|
|
|
# Check firewalld
|
|
success, output = self.run_cmd("firewall-cmd --state 2>/dev/null")
|
|
if success and "running" in output.lower():
|
|
self.audit_results['firewall'] = {'status': 'enabled', 'type': 'firewalld'}
|
|
return
|
|
|
|
# No firewall
|
|
self.add_issue(
|
|
"No Active Firewall",
|
|
"No firewall (iptables/ufw/firewalld) is currently active",
|
|
"HIGH",
|
|
"network",
|
|
fix_command="sudo ufw enable",
|
|
fix_instructions="Enable UFW: sudo ufw enable\nOr install: sudo apt install ufw && sudo ufw enable"
|
|
)
|
|
|
|
def check_ssh_config(self):
|
|
"""Check SSH hardening."""
|
|
self.print_status("Checking SSH configuration...")
|
|
|
|
ssh_config = Path("/etc/ssh/sshd_config")
|
|
if not ssh_config.exists():
|
|
self.audit_results['ssh'] = {'status': 'not_installed'}
|
|
return
|
|
|
|
try:
|
|
content = ssh_config.read_text()
|
|
except PermissionError:
|
|
success, content = self.run_cmd("sudo cat /etc/ssh/sshd_config 2>/dev/null")
|
|
if not success:
|
|
self.audit_results['ssh'] = {'status': 'permission_denied'}
|
|
return
|
|
|
|
self.audit_results['ssh'] = {'status': 'installed', 'issues': []}
|
|
|
|
# Check root login
|
|
if "PermitRootLogin no" not in content and "PermitRootLogin prohibit-password" not in content:
|
|
self.add_issue(
|
|
"SSH Root Login Enabled",
|
|
"Root login via SSH is not disabled",
|
|
"HIGH",
|
|
"ssh",
|
|
fix_command="sudo sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config && sudo systemctl restart sshd",
|
|
fix_instructions="Edit /etc/ssh/sshd_config:\n PermitRootLogin no\nThen restart: sudo systemctl restart sshd"
|
|
)
|
|
|
|
# Check password auth
|
|
if "PasswordAuthentication no" not in content:
|
|
self.add_issue(
|
|
"SSH Password Auth Enabled",
|
|
"Password authentication is enabled (key-based is more secure)",
|
|
"MEDIUM",
|
|
"ssh",
|
|
fix_command="sudo sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config && sudo systemctl restart sshd",
|
|
fix_instructions="Edit /etc/ssh/sshd_config:\n PasswordAuthentication no\nEnsure you have SSH keys set up first!"
|
|
)
|
|
|
|
# Check protocol version
|
|
if "Protocol 1" in content:
|
|
self.add_issue(
|
|
"SSH Protocol 1 Enabled",
|
|
"Insecure SSH Protocol 1 is enabled",
|
|
"CRITICAL",
|
|
"ssh",
|
|
fix_instructions="Remove 'Protocol 1' from /etc/ssh/sshd_config"
|
|
)
|
|
|
|
def check_open_ports(self):
|
|
"""Check for listening ports."""
|
|
self.print_status("Scanning open ports...")
|
|
|
|
success, output = self.run_cmd("ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null")
|
|
if not success:
|
|
return
|
|
|
|
lines = [l for l in output.split('\n') if 'LISTEN' in l]
|
|
self.audit_results['ports'] = {'listening': len(lines), 'high_risk': []}
|
|
|
|
high_risk_ports = {
|
|
'21': ('FTP', 'HIGH', 'FTP transmits credentials in plaintext'),
|
|
'23': ('Telnet', 'CRITICAL', 'Telnet is unencrypted'),
|
|
'69': ('TFTP', 'HIGH', 'TFTP has no authentication'),
|
|
'111': ('RPC', 'MEDIUM', 'RPC can expose services'),
|
|
'135': ('MS-RPC', 'HIGH', 'Windows RPC - potential attack vector'),
|
|
'139': ('NetBIOS', 'HIGH', 'NetBIOS session service'),
|
|
'445': ('SMB', 'HIGH', 'SMB - common attack target'),
|
|
'512': ('rexec', 'CRITICAL', 'Insecure remote execution'),
|
|
'513': ('rlogin', 'CRITICAL', 'Insecure remote login'),
|
|
'514': ('rsh', 'CRITICAL', 'Insecure remote shell'),
|
|
'1433': ('MSSQL', 'MEDIUM', 'Database port exposed'),
|
|
'3306': ('MySQL', 'MEDIUM', 'Database port exposed'),
|
|
'3389': ('RDP', 'HIGH', 'Remote Desktop exposed'),
|
|
'5432': ('PostgreSQL', 'MEDIUM', 'Database port exposed'),
|
|
'5900': ('VNC', 'HIGH', 'VNC often weakly configured'),
|
|
}
|
|
|
|
for line in lines:
|
|
for port, (name, severity, desc) in high_risk_ports.items():
|
|
if f':{port} ' in line or f':{port}\t' in line:
|
|
self.audit_results['ports']['high_risk'].append(port)
|
|
self.add_issue(
|
|
f"High-Risk Port Open: {port} ({name})",
|
|
desc,
|
|
severity,
|
|
"network",
|
|
fix_instructions=f"Disable the {name} service if not needed:\n sudo systemctl stop <service>\n sudo systemctl disable <service>"
|
|
)
|
|
|
|
def check_users(self):
|
|
"""Check user security."""
|
|
self.print_status("Checking user accounts...")
|
|
|
|
self.audit_results['users'] = {'issues': []}
|
|
|
|
# Users with UID 0
|
|
success, output = self.run_cmd("awk -F: '$3 == 0 {print $1}' /etc/passwd")
|
|
if success:
|
|
uid0_users = [u for u in output.split('\n') if u]
|
|
if len(uid0_users) > 1:
|
|
extra_roots = [u for u in uid0_users if u != 'root']
|
|
self.add_issue(
|
|
"Multiple Root Users",
|
|
f"Users with UID 0 besides root: {', '.join(extra_roots)}",
|
|
"CRITICAL",
|
|
"users",
|
|
fix_instructions="Review and remove extra UID 0 accounts:\n sudo vipw\n Change UID to non-zero or remove account"
|
|
)
|
|
|
|
# Empty passwords
|
|
success, output = self.run_cmd("sudo awk -F: '($2 == \"\" ) {print $1}' /etc/shadow 2>/dev/null")
|
|
if success and output:
|
|
empty = [u for u in output.split('\n') if u]
|
|
if empty:
|
|
self.add_issue(
|
|
"Users with Empty Passwords",
|
|
f"Accounts without passwords: {', '.join(empty)}",
|
|
"CRITICAL",
|
|
"users",
|
|
fix_instructions=f"Set passwords for these users:\n sudo passwd <username>\nOr lock the accounts:\n sudo usermod -L <username>"
|
|
)
|
|
|
|
# Users with shells that shouldn't have them
|
|
success, output = self.run_cmd("awk -F: '($7 != \"/usr/sbin/nologin\" && $7 != \"/bin/false\" && $7 != \"/sbin/nologin\") {print $1}' /etc/passwd")
|
|
if success:
|
|
shell_users = [u for u in output.split('\n') if u]
|
|
self.audit_results['users']['shell_users'] = len(shell_users)
|
|
|
|
def check_permissions(self):
|
|
"""Check critical file permissions."""
|
|
self.print_status("Checking file permissions...")
|
|
|
|
critical_files = [
|
|
("/etc/passwd", "644", "User database"),
|
|
("/etc/shadow", "640", "Password hashes"),
|
|
("/etc/group", "644", "Group database"),
|
|
("/etc/gshadow", "640", "Group passwords"),
|
|
("/etc/ssh/sshd_config", "600", "SSH configuration"),
|
|
("/root", "700", "Root home directory"),
|
|
("/etc/crontab", "600", "System crontab"),
|
|
]
|
|
|
|
self.audit_results['permissions'] = {'checked': 0, 'issues': 0}
|
|
|
|
for filepath, expected, desc in critical_files:
|
|
p = Path(filepath)
|
|
if p.exists():
|
|
self.audit_results['permissions']['checked'] += 1
|
|
try:
|
|
mode = oct(p.stat().st_mode)[-3:]
|
|
if int(mode) > int(expected):
|
|
self.audit_results['permissions']['issues'] += 1
|
|
self.add_issue(
|
|
f"Insecure Permissions: {filepath}",
|
|
f"{desc} has mode {mode} (should be {expected} or less)",
|
|
"MEDIUM",
|
|
"permissions",
|
|
fix_command=f"sudo chmod {expected} {filepath}",
|
|
fix_instructions=f"Fix permissions:\n sudo chmod {expected} {filepath}"
|
|
)
|
|
except:
|
|
pass
|
|
|
|
# Check for world-writable directories
|
|
success, output = self.run_cmd("find /etc -type f -perm -002 2>/dev/null | head -5")
|
|
if success and output:
|
|
files = output.split('\n')
|
|
self.add_issue(
|
|
"World-Writable Files in /etc",
|
|
f"Found {len(files)} world-writable files in /etc",
|
|
"HIGH",
|
|
"permissions",
|
|
fix_instructions="Review and fix permissions:\n find /etc -type f -perm -002 -exec chmod o-w {} \\;"
|
|
)
|
|
|
|
def check_services(self):
|
|
"""Check for unnecessary/dangerous services."""
|
|
self.print_status("Auditing services...")
|
|
|
|
dangerous_services = [
|
|
("telnet", "Telnet server"),
|
|
("rsh", "Remote shell"),
|
|
("rlogin", "Remote login"),
|
|
("tftp", "TFTP server"),
|
|
("vsftpd", "FTP server"),
|
|
("proftpd", "FTP server"),
|
|
("pure-ftpd", "FTP server"),
|
|
]
|
|
|
|
self.audit_results['services'] = {'dangerous_running': []}
|
|
|
|
for svc, desc in dangerous_services:
|
|
success, _ = self.run_cmd(f"systemctl is-active {svc} 2>/dev/null")
|
|
if success:
|
|
self.audit_results['services']['dangerous_running'].append(svc)
|
|
self.add_issue(
|
|
f"Dangerous Service Running: {svc}",
|
|
f"{desc} is running",
|
|
"HIGH",
|
|
"services",
|
|
fix_command=f"sudo systemctl stop {svc} && sudo systemctl disable {svc}",
|
|
fix_instructions=f"Stop and disable {svc}:\n sudo systemctl stop {svc}\n sudo systemctl disable {svc}"
|
|
)
|
|
|
|
def check_updates(self):
|
|
"""Check for available updates."""
|
|
self.print_status("Checking for updates...")
|
|
|
|
self.audit_results['updates'] = {'available': 0, 'security': 0}
|
|
|
|
os_id = self.system_info.get('os_id', '')
|
|
|
|
if os_id in ['debian', 'ubuntu', 'kali', 'mint']:
|
|
success, output = self.run_cmd("apt list --upgradable 2>/dev/null | grep -c upgradable || echo 0", timeout=30)
|
|
if success and output.isdigit():
|
|
count = int(output)
|
|
self.audit_results['updates']['available'] = count
|
|
if count > 50:
|
|
self.add_issue(
|
|
"Many Pending Updates",
|
|
f"{count} packages need updating",
|
|
"MEDIUM",
|
|
"updates",
|
|
fix_command="sudo apt update && sudo apt upgrade -y",
|
|
fix_instructions="Update system:\n sudo apt update\n sudo apt upgrade"
|
|
)
|
|
|
|
elif os_id in ['fedora', 'rhel', 'centos', 'rocky', 'alma']:
|
|
success, output = self.run_cmd("dnf check-update 2>/dev/null | wc -l", timeout=30)
|
|
if success and output.isdigit():
|
|
self.audit_results['updates']['available'] = int(output)
|
|
|
|
def check_fail2ban(self):
|
|
"""Check fail2ban status."""
|
|
self.print_status("Checking fail2ban...")
|
|
|
|
success, output = self.run_cmd("systemctl is-active fail2ban 2>/dev/null")
|
|
if success and "active" in output:
|
|
self.audit_results['fail2ban'] = {'status': 'running'}
|
|
else:
|
|
success, _ = self.run_cmd("which fail2ban-client 2>/dev/null")
|
|
if success:
|
|
self.add_issue(
|
|
"Fail2Ban Not Running",
|
|
"Fail2ban is installed but not running",
|
|
"MEDIUM",
|
|
"services",
|
|
fix_command="sudo systemctl start fail2ban && sudo systemctl enable fail2ban",
|
|
fix_instructions="Start fail2ban:\n sudo systemctl start fail2ban\n sudo systemctl enable fail2ban"
|
|
)
|
|
else:
|
|
self.add_issue(
|
|
"Fail2Ban Not Installed",
|
|
"Fail2ban is not installed (protects against brute-force)",
|
|
"LOW",
|
|
"services",
|
|
fix_command="sudo apt install fail2ban -y && sudo systemctl enable fail2ban && sudo systemctl start fail2ban",
|
|
fix_instructions="Install fail2ban:\n sudo apt install fail2ban\n sudo systemctl enable --now fail2ban"
|
|
)
|
|
|
|
def check_antivirus(self):
|
|
"""Check for antivirus."""
|
|
self.print_status("Checking antivirus...")
|
|
|
|
# Check ClamAV
|
|
success, _ = self.run_cmd("which clamscan 2>/dev/null")
|
|
if success:
|
|
self.audit_results['antivirus'] = {'status': 'installed', 'type': 'clamav'}
|
|
return
|
|
|
|
# Check for other AV
|
|
for av in ['sophos', 'eset', 'kaspersky']:
|
|
success, _ = self.run_cmd(f"which {av} 2>/dev/null")
|
|
if success:
|
|
self.audit_results['antivirus'] = {'status': 'installed', 'type': av}
|
|
return
|
|
|
|
self.add_issue(
|
|
"No Antivirus Installed",
|
|
"No antivirus solution detected",
|
|
"LOW",
|
|
"security",
|
|
fix_command="sudo apt install clamav clamav-daemon -y && sudo freshclam",
|
|
fix_instructions="Install ClamAV:\n sudo apt install clamav clamav-daemon\n sudo freshclam"
|
|
)
|
|
|
|
def check_cves(self, verbose: bool = True):
|
|
"""Check for CVEs affecting this system using local SQLite database."""
|
|
self.print_status("Checking CVE database for system vulnerabilities...")
|
|
|
|
# Get database stats
|
|
db_stats = self.cve_db.get_db_stats()
|
|
|
|
if db_stats['total_cves'] == 0:
|
|
if verbose:
|
|
print(f"{Colors.YELLOW}[!] Local CVE database is empty. Searching online...{Colors.RESET}")
|
|
# Fall back to online search
|
|
cves = self.cve_db.search_online(
|
|
cpe_name=self.cve_db.system_info.get('cpe_prefix', ''),
|
|
days_back=365,
|
|
max_results=100,
|
|
verbose=verbose
|
|
)
|
|
else:
|
|
if verbose:
|
|
print(f"{Colors.DIM} Local DB: {db_stats['total_cves']:,} CVEs | Last sync: {db_stats.get('last_sync', 'Never')[:10] if db_stats.get('last_sync') else 'Never'}{Colors.RESET}")
|
|
# Use local database
|
|
cves = self.cve_db.get_system_cves(max_results=100)
|
|
|
|
self.audit_results['cves'] = {
|
|
'total': len(cves),
|
|
'critical': sum(1 for c in cves if c.get('severity') == 'CRITICAL'),
|
|
'high': sum(1 for c in cves if c.get('severity') == 'HIGH'),
|
|
'medium': sum(1 for c in cves if c.get('severity') == 'MEDIUM'),
|
|
'low': sum(1 for c in cves if c.get('severity') == 'LOW'),
|
|
'items': cves[:20], # Keep top 20
|
|
'db_stats': db_stats,
|
|
}
|
|
|
|
if verbose:
|
|
print(f"{Colors.GREEN}[+] Found {len(cves)} CVEs for your system{Colors.RESET}")
|
|
|
|
# Add critical/high CVEs as issues
|
|
for cve in cves[:10]:
|
|
if cve.get('severity') in ['CRITICAL', 'HIGH']:
|
|
cve_id = cve.get('cve_id') or cve.get('id', '')
|
|
desc = cve.get('description', '')[:150]
|
|
self.add_issue(
|
|
f"CVE: {cve_id}",
|
|
desc,
|
|
cve['severity'],
|
|
"cve",
|
|
fix_instructions=f"Check: https://nvd.nist.gov/vuln/detail/{cve_id}\nUpdate affected packages and apply patches.",
|
|
score_penalty=15 if cve['severity'] == 'CRITICAL' else 10
|
|
)
|
|
|
|
# =========================================================================
|
|
# MAIN AUDIT
|
|
# =========================================================================
|
|
|
|
def run_full_audit(self, check_cves: bool = True):
|
|
"""Run complete system audit."""
|
|
clear_screen()
|
|
display_banner()
|
|
|
|
print(f"\n{Colors.BOLD}{Colors.CYAN}Starting Full System Security Audit{Colors.RESET}")
|
|
print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n")
|
|
|
|
self.issues = []
|
|
self.audit_results = {}
|
|
self.security_score = 100
|
|
|
|
# Collect info and run checks
|
|
self.collect_system_info()
|
|
self.check_firewall()
|
|
self.check_ssh_config()
|
|
self.check_open_ports()
|
|
self.check_users()
|
|
self.check_permissions()
|
|
self.check_services()
|
|
self.check_updates()
|
|
self.check_fail2ban()
|
|
self.check_antivirus()
|
|
|
|
if check_cves:
|
|
self.check_cves()
|
|
|
|
print(f"\n{Colors.DIM}{'─' * 50}{Colors.RESET}")
|
|
self.print_summary()
|
|
|
|
def print_summary(self):
|
|
"""Print audit summary."""
|
|
print(f"\n{Colors.BOLD}Security Audit Summary{Colors.RESET}")
|
|
print(f"{Colors.DIM}{'─' * 40}{Colors.RESET}")
|
|
|
|
# System info
|
|
print(f"\n{Colors.CYAN}System:{Colors.RESET} {self.system_info.get('os_name', 'Unknown')}")
|
|
print(f"{Colors.CYAN}Hostname:{Colors.RESET} {self.system_info.get('hostname', 'Unknown')}")
|
|
|
|
# Issue counts by severity
|
|
critical = sum(1 for i in self.issues if i.severity == 'CRITICAL')
|
|
high = sum(1 for i in self.issues if i.severity == 'HIGH')
|
|
medium = sum(1 for i in self.issues if i.severity == 'MEDIUM')
|
|
low = sum(1 for i in self.issues if i.severity == 'LOW')
|
|
|
|
print(f"\n{Colors.BOLD}Issues Found:{Colors.RESET}")
|
|
if critical:
|
|
print(f" {Colors.RED}CRITICAL: {critical}{Colors.RESET}")
|
|
if high:
|
|
print(f" {Colors.RED}HIGH: {high}{Colors.RESET}")
|
|
if medium:
|
|
print(f" {Colors.YELLOW}MEDIUM: {medium}{Colors.RESET}")
|
|
if low:
|
|
print(f" {Colors.CYAN}LOW: {low}{Colors.RESET}")
|
|
|
|
if not self.issues:
|
|
print(f" {Colors.GREEN}No issues found!{Colors.RESET}")
|
|
|
|
# Security score
|
|
print(f"\n{Colors.BOLD}Security Score: ", end="")
|
|
if self.security_score >= 80:
|
|
print(f"{Colors.GREEN}{self.security_score}/100{Colors.RESET}")
|
|
elif self.security_score >= 50:
|
|
print(f"{Colors.YELLOW}{self.security_score}/100{Colors.RESET}")
|
|
else:
|
|
print(f"{Colors.RED}{self.security_score}/100{Colors.RESET}")
|
|
|
|
def save_to_file(self) -> bool:
|
|
"""Save audit results to system.inf."""
|
|
try:
|
|
data = {
|
|
'audit_date': datetime.now().isoformat(),
|
|
'system_info': self.system_info,
|
|
'security_score': self.security_score,
|
|
'audit_results': self.audit_results,
|
|
'issues': [i.to_dict() for i in self.issues],
|
|
}
|
|
|
|
with open(self._system_inf_path(), 'w') as f:
|
|
json.dump(data, f, indent=2, default=str)
|
|
|
|
self.print_status(f"Results saved to {self._system_inf_path()}", "success")
|
|
return True
|
|
|
|
except Exception as e:
|
|
self.print_status(f"Failed to save: {e}", "error")
|
|
return False
|
|
|
|
def load_from_file(self) -> bool:
|
|
"""Load previous audit results from system.inf."""
|
|
if not self._system_inf_path().exists():
|
|
return False
|
|
|
|
try:
|
|
with open(self._system_inf_path(), 'r') as f:
|
|
data = json.load(f)
|
|
|
|
self.system_info = data.get('system_info', {})
|
|
self.security_score = data.get('security_score', 100)
|
|
self.audit_results = data.get('audit_results', {})
|
|
self.issues = [SecurityIssue.from_dict(i) for i in data.get('issues', [])]
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
self.print_status(f"Failed to load: {e}", "error")
|
|
return False
|
|
|
|
# =========================================================================
|
|
# ISSUE REMEDIATION
|
|
# =========================================================================
|
|
|
|
def show_issue_details(self, issue: SecurityIssue):
|
|
"""Show detailed information about an issue."""
|
|
clear_screen()
|
|
display_banner()
|
|
|
|
color = SecurityIssue.SEVERITY_COLORS.get(issue.severity, Colors.WHITE)
|
|
|
|
print(f"\n{Colors.BOLD}Issue Details{Colors.RESET}")
|
|
print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n")
|
|
|
|
print(f"{Colors.BOLD}Name:{Colors.RESET} {issue.name}")
|
|
print(f"{Colors.BOLD}Severity:{Colors.RESET} {color}{issue.severity}{Colors.RESET}")
|
|
print(f"{Colors.BOLD}Category:{Colors.RESET} {issue.category}")
|
|
print(f"\n{Colors.BOLD}Description:{Colors.RESET}")
|
|
print(f" {issue.description}")
|
|
|
|
if issue.cve_ids:
|
|
print(f"\n{Colors.BOLD}Related CVEs:{Colors.RESET}")
|
|
for cve in issue.cve_ids:
|
|
print(f" - {cve}")
|
|
|
|
if issue.fix_instructions:
|
|
print(f"\n{Colors.BOLD}Manual Fix Instructions:{Colors.RESET}")
|
|
for line in issue.fix_instructions.split('\n'):
|
|
print(f" {line}")
|
|
|
|
if issue.fix_command:
|
|
print(f"\n{Colors.BOLD}Auto-Fix Command:{Colors.RESET}")
|
|
print(f" {Colors.CYAN}{issue.fix_command}{Colors.RESET}")
|
|
|
|
print(f"\n{Colors.DIM}{'─' * 50}{Colors.RESET}")
|
|
|
|
def attempt_llm_fix(self, issue: SecurityIssue) -> bool:
|
|
"""Use LLM to generate and optionally apply a fix."""
|
|
try:
|
|
from core.llm import get_llm, LLMError
|
|
except ImportError:
|
|
self.print_status("LLM module not available", "error")
|
|
return False
|
|
|
|
self.print_status("Consulting LLM for fix recommendation...", "info")
|
|
|
|
try:
|
|
llm = get_llm()
|
|
|
|
if not llm.is_loaded:
|
|
self.print_status("Loading LLM model...", "info")
|
|
llm.load_model(verbose=True)
|
|
|
|
# Build prompt
|
|
prompt = f"""You are a Linux security expert. Analyze this security issue and provide a fix.
|
|
|
|
System: {self.system_info.get('os_name', 'Linux')}
|
|
Issue: {issue.name}
|
|
Severity: {issue.severity}
|
|
Description: {issue.description}
|
|
Category: {issue.category}
|
|
|
|
Provide:
|
|
1. A brief explanation of the risk
|
|
2. The exact command(s) to fix this issue
|
|
3. Any important warnings or prerequisites
|
|
|
|
Format your response clearly with sections."""
|
|
|
|
print(f"\n{Colors.CYAN}LLM Analysis:{Colors.RESET}\n")
|
|
|
|
# Generate response with streaming
|
|
response_text = ""
|
|
for token in llm.generate(prompt, stream=True, max_tokens=500):
|
|
print(token, end="", flush=True)
|
|
response_text += token
|
|
|
|
print("\n")
|
|
|
|
# Ask if user wants to apply suggested fix
|
|
if issue.fix_command:
|
|
print(f"\n{Colors.YELLOW}Suggested command:{Colors.RESET} {issue.fix_command}")
|
|
choice = input(f"\n{Colors.WHITE}Apply this fix? (y/n): {Colors.RESET}").strip().lower()
|
|
|
|
if choice == 'y':
|
|
print(f"\n{Colors.CYAN}[*] Executing: {issue.fix_command}{Colors.RESET}")
|
|
success, output = self.run_cmd(issue.fix_command, timeout=60)
|
|
|
|
if success:
|
|
self.print_status("Fix applied successfully!", "success")
|
|
issue.status = "fixed"
|
|
return True
|
|
else:
|
|
self.print_status(f"Command failed: {output}", "error")
|
|
return False
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
self.print_status(f"LLM error: {e}", "error")
|
|
return False
|
|
|
|
def apply_manual_fix(self, issue: SecurityIssue) -> bool:
|
|
"""Apply the predefined fix command."""
|
|
if not issue.fix_command:
|
|
self.print_status("No automatic fix available for this issue", "warning")
|
|
return False
|
|
|
|
print(f"\n{Colors.BOLD}Fix Command:{Colors.RESET}")
|
|
print(f" {Colors.CYAN}{issue.fix_command}{Colors.RESET}")
|
|
|
|
print(f"\n{Colors.YELLOW}Warning: This will modify your system.{Colors.RESET}")
|
|
choice = input(f"{Colors.WHITE}Execute this command? (y/n): {Colors.RESET}").strip().lower()
|
|
|
|
if choice != 'y':
|
|
self.print_status("Fix cancelled", "info")
|
|
return False
|
|
|
|
print(f"\n{Colors.CYAN}[*] Executing...{Colors.RESET}")
|
|
success, output = self.run_cmd(issue.fix_command, timeout=60)
|
|
|
|
if output:
|
|
print(f"\n{Colors.DIM}Output:{Colors.RESET}")
|
|
print(output)
|
|
|
|
if success:
|
|
self.print_status("Fix applied successfully!", "success")
|
|
issue.status = "fixed"
|
|
return True
|
|
else:
|
|
self.print_status("Command failed", "error")
|
|
return False
|
|
|
|
# =========================================================================
|
|
# MENU SYSTEM
|
|
# =========================================================================
|
|
|
|
def show_menu(self):
|
|
"""Display main menu."""
|
|
clear_screen()
|
|
display_banner()
|
|
|
|
# Load previous results if available
|
|
has_results = self._system_inf_path().exists()
|
|
|
|
# Get CVE database stats
|
|
db_stats = self.cve_db.get_db_stats()
|
|
|
|
print(f"\n{Colors.BLUE}{Colors.BOLD} My System - Security Audit{Colors.RESET}")
|
|
print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}")
|
|
|
|
# Show system info
|
|
sys_info = self.cve_db.get_system_info()
|
|
print(f"\n {Colors.CYAN}Detected:{Colors.RESET} {sys_info.get('os_id', 'unknown')} {sys_info.get('os_version', '')}")
|
|
print(f" {Colors.CYAN}Kernel:{Colors.RESET} {sys_info.get('kernel', 'unknown')}")
|
|
|
|
# Show CVE database status
|
|
if db_stats['total_cves'] > 0:
|
|
last_sync = db_stats.get('last_sync', '')[:10] if db_stats.get('last_sync') else 'Never'
|
|
print(f" {Colors.CYAN}CVE Database:{Colors.RESET} {db_stats['total_cves']:,} CVEs ({db_stats['db_size_mb']} MB)")
|
|
print(f" {Colors.CYAN}Last Sync:{Colors.RESET} {last_sync}")
|
|
else:
|
|
print(f" {Colors.YELLOW}CVE Database:{Colors.RESET} Empty - sync required")
|
|
|
|
if has_results and self.issues:
|
|
print(f" {Colors.CYAN}Last Score:{Colors.RESET} {self.security_score}/100")
|
|
print(f" {Colors.CYAN}Open Issues:{Colors.RESET} {sum(1 for i in self.issues if i.status == 'open')}")
|
|
|
|
print(f"\n{Colors.DIM} {'─' * 50}{Colors.RESET}\n")
|
|
|
|
print(f" {Colors.GREEN}[1]{Colors.RESET} Run Full System Audit")
|
|
print(f" {Colors.GREEN}[2]{Colors.RESET} Run Audit (Skip CVE Check)")
|
|
|
|
if has_results:
|
|
print(f"\n {Colors.CYAN}[3]{Colors.RESET} View Issues ({len(self.issues)} found)")
|
|
print(f" {Colors.CYAN}[4]{Colors.RESET} View CVE Report")
|
|
|
|
print(f"\n {Colors.YELLOW}[5]{Colors.RESET} Search CVE Database")
|
|
print(f" {Colors.YELLOW}[6]{Colors.RESET} Check Software for CVEs")
|
|
|
|
print(f"\n {Colors.MAGENTA}[7]{Colors.RESET} Sync CVE Database (Recent)")
|
|
print(f" {Colors.MAGENTA}[8]{Colors.RESET} Sync CVE Database (Full)")
|
|
print(f" {Colors.MAGENTA}[9]{Colors.RESET} CVE Database Info")
|
|
|
|
print(f"\n {Colors.DIM}[0]{Colors.RESET} Back to Main Menu")
|
|
print()
|
|
|
|
def show_issues_menu(self):
|
|
"""Display issues as selectable options."""
|
|
if not self.issues:
|
|
self.print_status("No issues found. Run an audit first.", "info")
|
|
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
|
|
return
|
|
|
|
while True:
|
|
clear_screen()
|
|
display_banner()
|
|
|
|
open_issues = [i for i in self.issues if i.status == 'open']
|
|
fixed_issues = [i for i in self.issues if i.status == 'fixed']
|
|
|
|
print(f"\n{Colors.BOLD}Security Issues{Colors.RESET}")
|
|
print(f"{Colors.DIM}Score: {self.security_score}/100 | Open: {len(open_issues)} | Fixed: {len(fixed_issues)}{Colors.RESET}")
|
|
print(f"{Colors.DIM}{'─' * 60}{Colors.RESET}\n")
|
|
|
|
if not open_issues:
|
|
print(f"{Colors.GREEN}All issues have been addressed!{Colors.RESET}")
|
|
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
|
|
return
|
|
|
|
# List issues
|
|
for idx, issue in enumerate(open_issues, 1):
|
|
color = SecurityIssue.SEVERITY_COLORS.get(issue.severity, Colors.WHITE)
|
|
severity_badge = f"{color}[{issue.severity[:4]}]{Colors.RESET}"
|
|
print(f" [{idx:2}] {severity_badge} {issue.name}")
|
|
|
|
print(f"\n {Colors.DIM}[0]{Colors.RESET} Back")
|
|
print()
|
|
|
|
try:
|
|
choice = input(f"{Colors.WHITE} Select issue to fix: {Colors.RESET}").strip()
|
|
|
|
if choice == '0':
|
|
break
|
|
|
|
if choice.isdigit():
|
|
idx = int(choice) - 1
|
|
if 0 <= idx < len(open_issues):
|
|
self.handle_issue(open_issues[idx])
|
|
|
|
except (EOFError, KeyboardInterrupt):
|
|
break
|
|
|
|
def handle_issue(self, issue: SecurityIssue):
|
|
"""Handle remediation of a single issue."""
|
|
self.show_issue_details(issue)
|
|
|
|
print(f"\n {Colors.GREEN}[1]{Colors.RESET} Auto-Fix with LLM")
|
|
print(f" {Colors.CYAN}[2]{Colors.RESET} Apply Manual Fix")
|
|
print(f" {Colors.YELLOW}[3]{Colors.RESET} Mark as Ignored")
|
|
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
|
|
print()
|
|
|
|
try:
|
|
choice = input(f"{Colors.WHITE} Select action: {Colors.RESET}").strip()
|
|
|
|
if choice == '1':
|
|
self.attempt_llm_fix(issue)
|
|
self.save_to_file()
|
|
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
|
|
|
|
elif choice == '2':
|
|
self.apply_manual_fix(issue)
|
|
self.save_to_file()
|
|
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
|
|
|
|
elif choice == '3':
|
|
issue.status = 'ignored'
|
|
self.print_status("Issue marked as ignored", "info")
|
|
self.save_to_file()
|
|
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
|
|
|
|
except (EOFError, KeyboardInterrupt):
|
|
pass
|
|
|
|
def show_cve_report(self):
|
|
"""Show CVE report from audit."""
|
|
clear_screen()
|
|
display_banner()
|
|
|
|
cve_data = self.audit_results.get('cves', {})
|
|
|
|
print(f"\n{Colors.BOLD}CVE Report for Your System{Colors.RESET}")
|
|
print(f"{Colors.DIM}{'─' * 60}{Colors.RESET}\n")
|
|
|
|
print(f" {Colors.CYAN}Total CVEs:{Colors.RESET} {cve_data.get('total', 0)}")
|
|
print(f" {Colors.RED}Critical:{Colors.RESET} {cve_data.get('critical', 0)}")
|
|
print(f" {Colors.RED}High:{Colors.RESET} {cve_data.get('high', 0)}")
|
|
print(f" {Colors.YELLOW}Medium:{Colors.RESET} {cve_data.get('medium', 0)}")
|
|
print(f" {Colors.CYAN}Low:{Colors.RESET} {cve_data.get('low', 0)}")
|
|
|
|
print(f"\n{Colors.BOLD}Top CVEs:{Colors.RESET}\n")
|
|
|
|
for cve in cve_data.get('items', [])[:15]:
|
|
color = SecurityIssue.SEVERITY_COLORS.get(cve['severity'], Colors.WHITE)
|
|
print(f" {color}{cve['id']}{Colors.RESET} ({cve['severity']}) - CVSS: {cve['cvss_score']}")
|
|
print(f" {Colors.DIM}{cve['description'][:70]}...{Colors.RESET}")
|
|
print()
|
|
|
|
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
|
|
|
|
def search_cve_interactive(self):
|
|
"""Interactive CVE search."""
|
|
clear_screen()
|
|
display_banner()
|
|
|
|
print(f"\n{Colors.BOLD}CVE Database Search{Colors.RESET}")
|
|
print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n")
|
|
|
|
db_stats = self.cve_db.get_db_stats()
|
|
if db_stats['total_cves'] > 0:
|
|
print(f"{Colors.DIM}Local database: {db_stats['total_cves']:,} CVEs{Colors.RESET}\n")
|
|
else:
|
|
print(f"{Colors.YELLOW}Local database empty - will search online{Colors.RESET}\n")
|
|
|
|
keyword = input(f"{Colors.WHITE}Search keyword (or Enter for system CVEs): {Colors.RESET}").strip()
|
|
|
|
print()
|
|
self.print_status("Searching CVE database...", "info")
|
|
|
|
# Try local database first, fall back to online
|
|
if db_stats['total_cves'] > 0:
|
|
if keyword:
|
|
cves = self.cve_db.search_cves(keyword=keyword)
|
|
else:
|
|
cves = self.cve_db.get_system_cves()
|
|
else:
|
|
if keyword:
|
|
cves = self.cve_db.search_online(keyword=keyword, verbose=True)
|
|
else:
|
|
cve_prefix = self.cve_db.system_info.get('cpe_prefix', '')
|
|
cves = self.cve_db.search_online(cpe_name=cve_prefix, verbose=True)
|
|
|
|
if not cves:
|
|
self.print_status("No CVEs found", "warning")
|
|
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
|
|
return
|
|
|
|
print(f"\n{Colors.BOLD}Results ({len(cves)} found):{Colors.RESET}\n")
|
|
|
|
for cve in cves[:20]:
|
|
color = SecurityIssue.SEVERITY_COLORS.get(cve.get('severity', ''), Colors.WHITE)
|
|
cve_id = cve.get('cve_id') or cve.get('id', '')
|
|
score = cve.get('cvss_score') or cve.get('cvss_v3_score') or 0
|
|
desc = cve.get('description', '')[:80]
|
|
print(f" {color}{cve_id}{Colors.RESET} - CVSS: {score} ({cve.get('severity', 'N/A')})")
|
|
print(f" {Colors.DIM}{desc}...{Colors.RESET}")
|
|
print()
|
|
|
|
# Option to view details
|
|
cve_id = input(f"\n{Colors.WHITE}Enter CVE ID for details (or Enter to go back): {Colors.RESET}").strip().upper()
|
|
|
|
if cve_id:
|
|
# Try local first, then online
|
|
details = self.cve_db.get_cve(cve_id)
|
|
if not details:
|
|
details = self.cve_db.fetch_cve_online(cve_id, verbose=True)
|
|
if details:
|
|
self.show_cve_details(details)
|
|
else:
|
|
self.print_status(f"CVE {cve_id} not found", "warning")
|
|
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
|
|
|
|
def show_cve_details(self, cve: Dict):
|
|
"""Show detailed CVE information."""
|
|
clear_screen()
|
|
display_banner()
|
|
|
|
cve_id = cve.get('cve_id') or cve.get('id', '')
|
|
print(f"\n{Colors.BOLD}{cve_id}{Colors.RESET}")
|
|
print(f"{Colors.DIM}{'─' * 60}{Colors.RESET}\n")
|
|
|
|
print(f"{Colors.BOLD}Description:{Colors.RESET}")
|
|
print(f" {cve.get('description', 'N/A')}\n")
|
|
|
|
# CVSS v3
|
|
if cve.get('cvss_v3_score'):
|
|
color = SecurityIssue.SEVERITY_COLORS.get(cve.get('cvss_v3_severity', ''), Colors.WHITE)
|
|
print(f"{Colors.BOLD}CVSS v3:{Colors.RESET}")
|
|
print(f" Score: {color}{cve['cvss_v3_score']} ({cve.get('cvss_v3_severity', 'N/A')}){Colors.RESET}")
|
|
if cve.get('cvss_v3_vector'):
|
|
print(f" Vector: {cve['cvss_v3_vector']}")
|
|
|
|
# CVSS v2 (if no v3)
|
|
elif cve.get('cvss_v2_score'):
|
|
color = SecurityIssue.SEVERITY_COLORS.get(cve.get('cvss_v2_severity', ''), Colors.WHITE)
|
|
print(f"{Colors.BOLD}CVSS v2:{Colors.RESET}")
|
|
print(f" Score: {color}{cve['cvss_v2_score']} ({cve.get('cvss_v2_severity', 'N/A')}){Colors.RESET}")
|
|
if cve.get('cvss_v2_vector'):
|
|
print(f" Vector: {cve['cvss_v2_vector']}")
|
|
|
|
if cve.get('published'):
|
|
print(f"\n{Colors.BOLD}Published:{Colors.RESET} {cve['published'][:10]}")
|
|
|
|
if cve.get('weaknesses'):
|
|
print(f"\n{Colors.BOLD}Weaknesses (CWE):{Colors.RESET}")
|
|
for w in cve['weaknesses'][:5]:
|
|
print(f" - {w}")
|
|
|
|
if cve.get('references'):
|
|
print(f"\n{Colors.BOLD}References:{Colors.RESET}")
|
|
for ref in cve['references'][:5]:
|
|
url = ref.get('url', ref) if isinstance(ref, dict) else ref
|
|
print(f" - {url}")
|
|
|
|
if cve.get('cpes'):
|
|
print(f"\n{Colors.BOLD}Affected Products:{Colors.RESET}")
|
|
for cpe in cve['cpes'][:5]:
|
|
criteria = cpe.get('cpe_criteria', cpe) if isinstance(cpe, dict) else cpe
|
|
print(f" - {criteria}")
|
|
|
|
print(f"\n{Colors.CYAN}Full details: https://nvd.nist.gov/vuln/detail/{cve_id}{Colors.RESET}")
|
|
|
|
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
|
|
|
|
def check_software_cves(self):
|
|
"""Check CVEs for specific software."""
|
|
clear_screen()
|
|
display_banner()
|
|
|
|
print(f"\n{Colors.BOLD}Software CVE Check{Colors.RESET}")
|
|
print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n")
|
|
|
|
db_stats = self.cve_db.get_db_stats()
|
|
if db_stats['total_cves'] > 0:
|
|
print(f"{Colors.DIM}Local database: {db_stats['total_cves']:,} CVEs{Colors.RESET}\n")
|
|
|
|
software = input(f"{Colors.WHITE}Software name (e.g., apache, nginx, openssh): {Colors.RESET}").strip()
|
|
if not software:
|
|
return
|
|
|
|
version = input(f"{Colors.WHITE}Version (optional): {Colors.RESET}").strip()
|
|
|
|
print()
|
|
self.print_status(f"Searching CVEs for {software}...", "info")
|
|
|
|
# Try local database first
|
|
if db_stats['total_cves'] > 0:
|
|
cves = self.cve_db.get_software_cves(software, version=version if version else None)
|
|
else:
|
|
# Fall back to online search
|
|
keyword = f"{software} {version}" if version else software
|
|
cves = self.cve_db.search_online(keyword=keyword, verbose=True)
|
|
|
|
if not cves:
|
|
self.print_status("No CVEs found for this software", "success")
|
|
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
|
|
return
|
|
|
|
print(f"\n{Colors.BOLD}Found {len(cves)} CVEs:{Colors.RESET}\n")
|
|
|
|
for cve in cves[:15]:
|
|
color = SecurityIssue.SEVERITY_COLORS.get(cve.get('severity', ''), Colors.WHITE)
|
|
cve_id = cve.get('cve_id') or cve.get('id', '')
|
|
score = cve.get('cvss_score') or cve.get('cvss_v3_score') or 0
|
|
desc = cve.get('description', '')
|
|
desc = desc[:70] + '...' if len(desc) > 70 else desc
|
|
print(f" {color}{cve_id}{Colors.RESET} - CVSS: {score} ({cve.get('severity', 'N/A')})")
|
|
print(f" {Colors.DIM}{desc}{Colors.RESET}")
|
|
print()
|
|
|
|
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
|
|
|
|
def sync_database_recent(self):
|
|
"""Sync recent CVEs (last 120 days)."""
|
|
clear_screen()
|
|
display_banner()
|
|
|
|
print(f"\n{Colors.BOLD}CVE Database Sync (Recent){Colors.RESET}")
|
|
print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n")
|
|
|
|
print(f"This will download CVEs from the last 120 days.")
|
|
print(f"Estimated time: 5-15 minutes (depending on API rate limits)\n")
|
|
|
|
confirm = input(f"{Colors.WHITE}Start sync? (y/n): {Colors.RESET}").strip().lower()
|
|
if confirm != 'y':
|
|
return
|
|
|
|
print()
|
|
stats = self.cve_db.sync_database(days_back=120, verbose=True)
|
|
|
|
print(f"\n{Colors.BOLD}Sync Complete{Colors.RESET}")
|
|
print(f" CVEs processed: {stats.get('cves_processed', 0):,}")
|
|
print(f" Errors: {stats.get('errors', 0)}")
|
|
|
|
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
|
|
|
|
def sync_database_full(self):
|
|
"""Full database sync (all CVEs since 1999)."""
|
|
clear_screen()
|
|
display_banner()
|
|
|
|
print(f"\n{Colors.BOLD}CVE Database Sync (Full){Colors.RESET}")
|
|
print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n")
|
|
|
|
print(f"{Colors.YELLOW}WARNING: This will download ALL CVEs (200,000+){Colors.RESET}")
|
|
print(f"Estimated time: 2-6 hours (depending on API rate limits)")
|
|
print(f"Database size: ~150-300 MB\n")
|
|
|
|
print(f"Consider getting an NVD API key for faster sync:")
|
|
print(f" https://nvd.nist.gov/developers/request-an-api-key\n")
|
|
|
|
confirm = input(f"{Colors.WHITE}Start full sync? (y/n): {Colors.RESET}").strip().lower()
|
|
if confirm != 'y':
|
|
return
|
|
|
|
print()
|
|
stats = self.cve_db.sync_database(full_sync=True, verbose=True)
|
|
|
|
print(f"\n{Colors.BOLD}Sync Complete{Colors.RESET}")
|
|
print(f" CVEs processed: {stats.get('cves_processed', 0):,}")
|
|
print(f" Errors: {stats.get('errors', 0)}")
|
|
|
|
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
|
|
|
|
def show_database_info(self):
|
|
"""Show CVE database information."""
|
|
clear_screen()
|
|
display_banner()
|
|
|
|
print(f"\n{Colors.BOLD}CVE Database Information{Colors.RESET}")
|
|
print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n")
|
|
|
|
stats = self.cve_db.get_db_stats()
|
|
|
|
print(f" {Colors.CYAN}Database Path:{Colors.RESET} {stats['db_path']}")
|
|
print(f" {Colors.CYAN}Database Size:{Colors.RESET} {stats['db_size_mb']} MB")
|
|
print(f" {Colors.CYAN}Total CVEs:{Colors.RESET} {stats['total_cves']:,}")
|
|
print(f" {Colors.CYAN}Total CPEs:{Colors.RESET} {stats['total_cpes']:,}")
|
|
print(f" {Colors.CYAN}Last Sync:{Colors.RESET} {stats.get('last_sync', 'Never')}")
|
|
|
|
if stats.get('by_severity'):
|
|
print(f"\n {Colors.BOLD}CVEs by Severity:{Colors.RESET}")
|
|
for sev, count in sorted(stats['by_severity'].items()):
|
|
color = SecurityIssue.SEVERITY_COLORS.get(sev, Colors.WHITE)
|
|
print(f" {color}{sev}:{Colors.RESET} {count:,}")
|
|
|
|
sys_info = self.cve_db.get_system_info()
|
|
print(f"\n {Colors.BOLD}System Detection:{Colors.RESET}")
|
|
print(f" OS: {sys_info.get('os_name', 'Unknown')}")
|
|
print(f" CPE: {sys_info.get('cpe_prefix', 'Unknown')}")
|
|
|
|
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
|
|
|
|
def run(self):
|
|
"""Main module loop."""
|
|
# Try to load previous results
|
|
self.load_from_file()
|
|
|
|
while True:
|
|
self.show_menu()
|
|
|
|
try:
|
|
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip()
|
|
|
|
if choice == '0':
|
|
break
|
|
|
|
elif choice == '1':
|
|
self.run_full_audit(check_cves=True)
|
|
self.save_to_file()
|
|
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
|
|
|
|
elif choice == '2':
|
|
self.run_full_audit(check_cves=False)
|
|
self.save_to_file()
|
|
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
|
|
|
|
elif choice == '3' and self._system_inf_path().exists():
|
|
self.show_issues_menu()
|
|
|
|
elif choice == '4' and self._system_inf_path().exists():
|
|
self.show_cve_report()
|
|
|
|
elif choice == '5':
|
|
self.search_cve_interactive()
|
|
|
|
elif choice == '6':
|
|
self.check_software_cves()
|
|
|
|
elif choice == '7':
|
|
self.sync_database_recent()
|
|
|
|
elif choice == '8':
|
|
self.sync_database_full()
|
|
|
|
elif choice == '9':
|
|
self.show_database_info()
|
|
|
|
except (EOFError, KeyboardInterrupt):
|
|
break
|
|
|
|
|
|
def run():
|
|
MySystem().run()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run()
|