Files
T

246 lines
9.1 KiB
Python

#!/usr/bin/env python3
"""Rapid7 InsightVM vulnerability scanning agent.
Interfaces with the InsightVM (Nexpose) REST API to manage scan
configurations, launch scans, retrieve vulnerability results, and
generate remediation reports. Supports site management, asset
discovery, and vulnerability prioritization.
"""
import argparse
import json
import os
import sys
import time
from datetime import datetime, timezone
try:
import requests
from requests.auth import HTTPBasicAuth
except ImportError:
print("[!] 'requests' required: pip install requests", file=sys.stderr)
sys.exit(1)
def get_insightvm_config():
"""Return InsightVM API connection config."""
host = os.environ.get("INSIGHTVM_HOST", "localhost")
port = os.environ.get("INSIGHTVM_PORT", "3780")
user = os.environ.get("INSIGHTVM_USER", "")
password = os.environ.get("INSIGHTVM_PASSWORD", "")
return f"https://{host}:{port}", user, password
def api_call(base_url, endpoint, user, password, method="GET",
data=None, params=None):
"""Make authenticated API call to InsightVM."""
url = f"{base_url}/api/3{endpoint}"
auth = HTTPBasicAuth(user, password)
headers = {"Content-Type": "application/json", "Accept": "application/json"}
if method == "POST":
resp = requests.post(url, auth=auth, headers=headers, json=data,
params=params,
verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=60) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
elif method == "PUT":
resp = requests.put(url, auth=auth, headers=headers, json=data,
verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=60) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
else:
resp = requests.get(url, auth=auth, headers=headers, params=params,
verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=60) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
resp.raise_for_status()
return resp.json()
def list_sites(base_url, user, password):
"""List all scan sites."""
print("[*] Listing sites...")
data = api_call(base_url, "/sites", user, password, params={"size": 500})
sites = []
for site in data.get("resources", []):
sites.append({
"id": site.get("id"),
"name": site.get("name", ""),
"description": site.get("description", ""),
"type": site.get("type", ""),
"assets": site.get("assets", 0),
"last_scan_time": site.get("lastScanTime", ""),
"risk_score": site.get("riskScore", 0),
})
print(f"[+] Found {len(sites)} sites")
return sites
def get_site_vulnerabilities(base_url, user, password, site_id):
"""Get vulnerabilities for a specific site."""
print(f"[*] Fetching vulnerabilities for site {site_id}...")
vulns = []
page = 0
while True:
data = api_call(base_url, f"/sites/{site_id}/vulnerabilities",
user, password, params={"page": page, "size": 100})
resources = data.get("resources", [])
if not resources:
break
for v in resources:
vulns.append({
"id": v.get("id", ""),
"title": v.get("title", ""),
"severity": v.get("severity", ""),
"cvss_v3_score": v.get("cvss", {}).get("v3", {}).get("score", 0),
"risk_score": v.get("riskScore", 0),
"instances": v.get("instances", 0),
"status": v.get("status", ""),
})
page += 1
total_pages = data.get("page", {}).get("totalPages", 1)
if page >= total_pages:
break
print(f"[+] Retrieved {len(vulns)} vulnerabilities")
return vulns
def launch_scan(base_url, user, password, site_id, scan_name=None):
"""Launch a scan on a site."""
if not scan_name:
scan_name = f"agent-scan-{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')}"
print(f"[*] Launching scan '{scan_name}' on site {site_id}...")
data = api_call(base_url, f"/sites/{site_id}/scans", user, password,
method="POST", data={"name": scan_name})
scan_id = data.get("id")
print(f"[+] Scan started, ID: {scan_id}")
return scan_id
def poll_scan_status(base_url, user, password, scan_id, max_wait=1800):
"""Poll scan status until completion."""
print(f"[*] Waiting for scan {scan_id} to complete...")
elapsed = 0
interval = 30
while elapsed < max_wait:
data = api_call(base_url, f"/scans/{scan_id}", user, password)
status = data.get("status", "unknown")
if status in ("finished", "stopped", "error"):
print(f"[+] Scan {status}")
return status, data
print(f" Status: {status} ({elapsed}s)")
time.sleep(interval)
elapsed += interval
print("[!] Scan timed out")
return "timeout", {}
def get_scan_report(base_url, user, password, scan_id):
"""Get scan results summary."""
data = api_call(base_url, f"/scans/{scan_id}", user, password)
return {
"scan_id": scan_id,
"status": data.get("status", ""),
"start_time": data.get("startTime", ""),
"end_time": data.get("endTime", ""),
"duration": data.get("duration", ""),
"assets_discovered": data.get("assets", 0),
"vulnerabilities": data.get("vulnerabilities", {}),
}
def format_summary(sites, vulns=None, scan_report=None):
"""Print summary."""
print(f"\n{'='*60}")
print(f" Rapid7 InsightVM Report")
print(f"{'='*60}")
if sites:
print(f"\n Sites ({len(sites)}):")
for s in sites:
print(f" {s['name']:30s} | Assets: {s['assets']:5d} | "
f"Risk: {s['risk_score']:8.1f}")
if vulns:
severity_counts = {}
for v in vulns:
sev = v.get("severity", "unknown")
severity_counts[sev] = severity_counts.get(sev, 0) + 1
print(f"\n Vulnerabilities ({len(vulns)}):")
for sev in sorted(severity_counts.keys()):
print(f" {sev:15s}: {severity_counts[sev]}")
if scan_report:
print(f"\n Scan Report:")
print(f" Status : {scan_report.get('status', 'N/A')}")
print(f" Assets : {scan_report.get('assets_discovered', 0)}")
def main():
parser = argparse.ArgumentParser(description="Rapid7 InsightVM scanning agent")
sub = parser.add_subparsers(dest="command")
sub.add_parser("list-sites", help="List scan sites")
p_vulns = sub.add_parser("get-vulns", help="Get site vulnerabilities")
p_vulns.add_argument("--site-id", required=True, type=int)
p_scan = sub.add_parser("scan", help="Launch a scan")
p_scan.add_argument("--site-id", required=True, type=int)
p_scan.add_argument("--wait", action="store_true", help="Wait for completion")
p_scan.add_argument("--max-wait", type=int, default=1800)
parser.add_argument("--host", help="InsightVM host (or INSIGHTVM_HOST env)")
parser.add_argument("--user", help="Username (or INSIGHTVM_USER env)")
parser.add_argument("--password", help="Password (or INSIGHTVM_PASSWORD env)")
parser.add_argument("--output", "-o", help="Output JSON report")
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()
if not args.command:
parser.print_help()
sys.exit(1)
if args.host:
os.environ["INSIGHTVM_HOST"] = args.host
if args.user:
os.environ["INSIGHTVM_USER"] = args.user
if args.password:
os.environ["INSIGHTVM_PASSWORD"] = args.password
base_url, user, password = get_insightvm_config()
if not user or not password:
print("[!] Set INSIGHTVM_USER and INSIGHTVM_PASSWORD", file=sys.stderr)
sys.exit(1)
result = {}
if args.command == "list-sites":
sites = list_sites(base_url, user, password)
format_summary(sites)
result = {"sites": sites}
elif args.command == "get-vulns":
vulns = get_site_vulnerabilities(base_url, user, password, args.site_id)
format_summary([], vulns)
result = {"site_id": args.site_id, "vulnerabilities": vulns}
elif args.command == "scan":
scan_id = launch_scan(base_url, user, password, args.site_id)
if args.wait:
status, data = poll_scan_status(base_url, user, password, scan_id, args.max_wait)
scan_report = get_scan_report(base_url, user, password, scan_id)
format_summary([], scan_report=scan_report)
result = {"scan_id": scan_id, "report": scan_report}
else:
result = {"scan_id": scan_id, "status": "launched"}
report = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"tool": "Rapid7 InsightVM",
"command": args.command,
"result": result,
}
if args.output:
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
print(f"\n[+] Report saved to {args.output}")
elif args.verbose:
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()