Files
T

220 lines
7.6 KiB
Python

#!/usr/bin/env python3
"""
Agentless Vulnerability Scanning Orchestrator
Performs SSH-based agentless vulnerability scanning on Linux hosts
by enumerating packages and checking against known vulnerabilities.
Requirements:
pip install paramiko requests pandas
Usage:
python process.py scan --hosts hosts.txt --key /path/to/ssh_key
python process.py check --host 192.168.1.10 --user scanner --key /path/to/ssh_key
python process.py report --input scan_results.json --output report.csv
"""
import argparse
import json
import sys
from datetime import datetime
import pandas as pd
import paramiko
import requests
NVD_API = "https://services.nvd.nist.gov/rest/json/cves/2.0"
class AgentlessScanner:
"""SSH-based agentless vulnerability scanner."""
def __init__(self, key_path, username="scanner"):
self.key_path = key_path
self.username = username
def _connect(self, hostname, port=22):
"""Establish SSH connection."""
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
key = paramiko.Ed25519Key.from_private_key_file(self.key_path)
except paramiko.ssh_exception.SSHException:
key = paramiko.RSAKey.from_private_key_file(self.key_path)
client.connect(hostname, port=port, username=self.username,
pkey=key, timeout=30)
return client
def _exec(self, client, command, timeout=30):
"""Execute command and return output."""
_, stdout, stderr = client.exec_command(command, timeout=timeout)
return stdout.read().decode().strip(), stderr.read().decode().strip()
def get_os_info(self, client):
"""Detect OS distribution and version."""
out, _ = self._exec(client, "cat /etc/os-release")
info = {}
for line in out.split("\n"):
if "=" in line:
k, v = line.split("=", 1)
info[k] = v.strip('"')
return info
def get_packages_dpkg(self, client):
"""Get packages via dpkg (Debian/Ubuntu)."""
out, _ = self._exec(
client,
"dpkg-query -W -f='${Package}|${Version}|${Architecture}\\n'"
)
packages = []
for line in out.split("\n"):
parts = line.split("|")
if len(parts) >= 2:
packages.append({
"name": parts[0],
"version": parts[1],
"arch": parts[2] if len(parts) > 2 else "",
})
return packages
def get_packages_rpm(self, client):
"""Get packages via rpm (RHEL/CentOS/Fedora)."""
out, _ = self._exec(
client,
"rpm -qa --queryformat '%{NAME}|%{VERSION}-%{RELEASE}|%{ARCH}\\n'"
)
packages = []
for line in out.split("\n"):
parts = line.split("|")
if len(parts) >= 2:
packages.append({
"name": parts[0],
"version": parts[1],
"arch": parts[2] if len(parts) > 2 else "",
})
return packages
def get_kernel(self, client):
"""Get running kernel version."""
out, _ = self._exec(client, "uname -r")
return out
def get_listening_services(self, client):
"""Get listening network services."""
out, _ = self._exec(client, "ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null")
return out
def scan_host(self, hostname, port=22):
"""Perform full agentless scan."""
result = {
"hostname": hostname,
"scan_time": datetime.utcnow().isoformat(),
"status": "success",
"os_info": {},
"kernel": "",
"packages": [],
"listening_services": "",
}
try:
client = self._connect(hostname, port)
result["os_info"] = self.get_os_info(client)
result["kernel"] = self.get_kernel(client)
os_id = result["os_info"].get("ID", "").lower()
if os_id in ("ubuntu", "debian", "kali"):
result["packages"] = self.get_packages_dpkg(client)
else:
result["packages"] = self.get_packages_rpm(client)
result["listening_services"] = self.get_listening_services(client)
client.close()
print(f" [+] {hostname}: {result['os_info'].get('PRETTY_NAME', 'Unknown')} | "
f"{len(result['packages'])} packages | kernel {result['kernel']}")
except Exception as e:
result["status"] = "error"
result["error"] = str(e)
print(f" [!] {hostname}: {e}")
return result
def scan_hosts(self, hosts, port=22):
"""Scan multiple hosts."""
results = []
for host in hosts:
host = host.strip()
if not host or host.startswith("#"):
continue
print(f"[*] Scanning {host}...")
results.append(self.scan_host(host, port))
return results
def main():
parser = argparse.ArgumentParser(
description="Agentless Vulnerability Scanning Orchestrator"
)
subparsers = parser.add_subparsers(dest="command")
scan_p = subparsers.add_parser("scan", help="Scan hosts from file")
scan_p.add_argument("--hosts", required=True, help="File with hostnames/IPs")
scan_p.add_argument("--key", required=True, help="SSH private key path")
scan_p.add_argument("--user", default="scanner", help="SSH username")
scan_p.add_argument("--port", type=int, default=22, help="SSH port")
scan_p.add_argument("--output", default="scan_results.json")
check_p = subparsers.add_parser("check", help="Scan a single host")
check_p.add_argument("--host", required=True)
check_p.add_argument("--key", required=True)
check_p.add_argument("--user", default="scanner")
check_p.add_argument("--port", type=int, default=22)
report_p = subparsers.add_parser("report", help="Generate CSV report")
report_p.add_argument("--input", required=True, help="Scan results JSON")
report_p.add_argument("--output", default="scan_report.csv")
args = parser.parse_args()
if args.command == "scan":
scanner = AgentlessScanner(args.key, args.user)
with open(args.hosts) as f:
hosts = f.readlines()
results = scanner.scan_hosts(hosts, args.port)
with open(args.output, "w") as f:
json.dump(results, f, indent=2)
print(f"\n[+] Results saved to {args.output}")
successful = sum(1 for r in results if r["status"] == "success")
print(f" Scanned: {len(results)} | Success: {successful}")
elif args.command == "check":
scanner = AgentlessScanner(args.key, args.user)
result = scanner.scan_host(args.host, args.port)
print(json.dumps(result, indent=2, default=str))
elif args.command == "report":
with open(args.input) as f:
results = json.load(f)
rows = []
for r in results:
for pkg in r.get("packages", []):
rows.append({
"hostname": r["hostname"],
"os": r.get("os_info", {}).get("PRETTY_NAME", ""),
"kernel": r.get("kernel", ""),
"package": pkg["name"],
"version": pkg["version"],
})
df = pd.DataFrame(rows)
df.to_csv(args.output, index=False)
print(f"[+] Report with {len(rows)} package entries saved to {args.output}")
else:
parser.print_help()
if __name__ == "__main__":
main()