#!/usr/bin/env python3 """Authenticated vulnerability scan orchestration agent using Nessus API.""" import json import os import sys import argparse from datetime import datetime try: import requests import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) except ImportError: print("Install: pip install requests") sys.exit(1) class NessusClient: """Client for Tenable Nessus REST API.""" def __init__(self, url, access_key, secret_key): self.base_url = url.rstrip("/") self.headers = { "X-ApiKeys": f"accessKey={access_key}; secretKey={secret_key}", "Content-Type": "application/json", } def _get(self, path, params=None): resp = requests.get(f"{self.base_url}{path}", headers=self.headers, params=params, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() return resp.json() def _post(self, path, data=None): resp = requests.post(f"{self.base_url}{path}", headers=self.headers, json=data, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() return resp.json() def list_scans(self): return self._get("/scans").get("scans", []) def get_scan_details(self, scan_id): return self._get(f"/scans/{scan_id}") def list_policies(self): return self._get("/policies").get("policies", []) def list_credentials(self): return self._get("/credentials").get("credentials", []) def get_scan_results(self, scan_id): details = self.get_scan_details(scan_id) hosts = details.get("hosts", []) vulns = details.get("vulnerabilities", []) return {"hosts": hosts, "vulnerabilities": vulns, "info": details.get("info", {})} def get_host_vulnerabilities(self, scan_id, host_id): return self._get(f"/scans/{scan_id}/hosts/{host_id}") def export_scan(self, scan_id, fmt="nessus"): data = {"format": fmt} resp = self._post(f"/scans/{scan_id}/export", data) return resp.get("file", 0) def analyze_scan_results(results): """Analyze scan results and categorize findings.""" vulns = results.get("vulnerabilities", []) severity_map = {0: "Info", 1: "Low", 2: "Medium", 3: "High", 4: "Critical"} severity_counts = {"Critical": 0, "High": 0, "Medium": 0, "Low": 0, "Info": 0} categorized = [] for v in vulns: sev = severity_map.get(v.get("severity", 0), "Info") severity_counts[sev] += 1 categorized.append({ "plugin_id": v.get("plugin_id", 0), "plugin_name": v.get("plugin_name", ""), "severity": sev, "severity_index": v.get("severity", 0), "count": v.get("count", 0), "family": v.get("plugin_family", ""), }) categorized.sort(key=lambda x: x["severity_index"], reverse=True) return {"severity_counts": severity_counts, "vulnerabilities": categorized} def audit_credential_coverage(results): """Check if authenticated scan achieved credential coverage.""" findings = [] hosts = results.get("hosts", []) for host in hosts: host_info = host.get("info", {}) credentialed = host.get("credentialed_checks_running", "") if not credentialed or credentialed == "no": findings.append({ "host": host.get("hostname", host.get("host_id", "")), "issue": "Credentialed checks not running — scan is unauthenticated", "severity": "HIGH", "detail": "Configure valid credentials for this host", }) return findings def compare_auth_vs_unauth(auth_results, unauth_results): """Compare authenticated vs unauthenticated scan finding counts.""" auth_vulns = len(auth_results.get("vulnerabilities", [])) unauth_vulns = len(unauth_results.get("vulnerabilities", [])) improvement = ((auth_vulns - unauth_vulns) / max(unauth_vulns, 1)) * 100 return { "authenticated_findings": auth_vulns, "unauthenticated_findings": unauth_vulns, "improvement_pct": round(improvement, 1), "additional_findings": auth_vulns - unauth_vulns, } def run_audit(args): """Execute authenticated vulnerability scan audit.""" print(f"\n{'='*60}") print(f" AUTHENTICATED VULNERABILITY SCAN AUDIT") print(f" Generated: {datetime.utcnow().isoformat()} UTC") print(f"{'='*60}\n") client = NessusClient(args.nessus_url, args.access_key, args.secret_key) report = {} scans = client.list_scans() report["total_scans"] = len(scans) if scans else 0 print(f"--- AVAILABLE SCANS ({report['total_scans']}) ---") for s in (scans or [])[:10]: print(f" [{s.get('status','')}] {s.get('name','')}: {s.get('id','')}") if args.scan_id: results = client.get_scan_results(args.scan_id) analysis = analyze_scan_results(results) report["analysis"] = analysis counts = analysis["severity_counts"] print(f"\n--- SCAN RESULTS (ID: {args.scan_id}) ---") print(f" Critical: {counts['Critical']} | High: {counts['High']} | " f"Medium: {counts['Medium']} | Low: {counts['Low']}") print(f"\n--- TOP VULNERABILITIES ---") for v in analysis["vulnerabilities"][:15]: print(f" [{v['severity']}] {v['plugin_name'][:70]} (x{v['count']})") cred_check = audit_credential_coverage(results) report["credential_coverage"] = cred_check if cred_check: print(f"\n--- CREDENTIAL COVERAGE ISSUES ({len(cred_check)}) ---") for c in cred_check: print(f" [{c['severity']}] {c['host']}: {c['issue']}") else: print(f"\n Credential coverage: All hosts authenticated") return report def main(): parser = argparse.ArgumentParser(description="Authenticated Vulnerability Scan Agent") parser.add_argument("--nessus-url", required=True, help="Nessus server URL") parser.add_argument("--access-key", required=True, help="Nessus API access key") parser.add_argument("--secret-key", required=True, help="Nessus API secret key") parser.add_argument("--scan-id", type=int, help="Scan ID to analyze results") parser.add_argument("--output", help="Save report to JSON file") args = parser.parse_args() report = run_audit(args) if args.output: with open(args.output, "w") as f: json.dump(report, f, indent=2, default=str) print(f"\n[+] Report saved to {args.output}") if __name__ == "__main__": main()