Skip to content

Instantly share code, notes, and snippets.

@colehocking
Created April 29, 2025 21:22
Show Gist options
  • Select an option

  • Save colehocking/1e2a6ab8a930d7f638f77eaeb7c5e7c7 to your computer and use it in GitHub Desktop.

Select an option

Save colehocking/1e2a6ab8a930d7f638f77eaeb7c5e7c7 to your computer and use it in GitHub Desktop.
Create a vulnerability spreadsheet for a list of servers as input
#!/usr/bin/python3
# Grab vulnerability reports for a list of hosts from Rapid7 InsightIVM
# usage: ./vuln_reports.py -f <hostfile>
# -- Cole Hocking
import xlsxwriter, configparser, argparse, requests, json, os, urllib3, re
from requests.auth import HTTPBasicAuth
# Disable warning from self-signed cert
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def is_between(num, lower_bound, upper_bound):
"""
Generate the CVSS eval for severity
return: boolean
"""
return lower_bound <= num <= upper_bound
def sanitize_name(host):
"""
Sanitize excel sheet name
Worksheet names must be < 32 chars, not containing: '[]:*?\/'
:return string sanitized hostname
"""
maxlength = 31
badchars = "[]:*?\/"
translate_table = str.maketrans("", "", badchars)
host = host.translate(translate_table)
# strip chars longer than maxlength
if len(host) > maxlength:
host = host[:maxlength]
return host
else:
return host
def is_ipv4(host):
"""
Check if hostname is actually IPv4 address
:return boolean
"""
# Define an IPv4 via regex
ipv4_pattern = r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
if re.match(ipv4_pattern, host):
return True
else:
return False
def get_host_ids(base_url, uname, passw, hostfile):
"""
Fetch Host IDs for a list of assets
:return dict hostid key & hostname value
"""
# POST to search asset and fetch ID
url = f'{base_url}api/3/assets/search'
# list of hostnames
hosts = {}
try:
with open(hostfile, 'r') as host_list:
for host in host_list:
if is_ipv4(host):
# Query parameters for IPv4
# query: ip-address is <host>
data = {
"match": "all",
"filters": [
{ "field": "ip-address", "operator": "is", "value": host }
]
}
else:
#remove leading/trailing whitespace
host = str(host.strip())
# Query parameters for hostname
# query: host-name contains <host>
data = {
"match": "all",
"filters": [
{ "field": "host-name", "operator": "contains", "value": host }
]
}
# run the search to get the IDs
try:
response = requests.post(url,
auth=HTTPBasicAuth(uname, passw), json=data, verify=False)
except requests.exceptions.RequestException as e:
print(f"An error occurred: {e}")
sys.exit(1)
result = response.json()
# if there was a resources list, the host was found
if len(result["resources"]) > 0:
hostid = result["resources"][0]["id"]
# should return an integer
if type(hostid) == int:
# add hostid key and host value
hosts[hostid] = host
else:
print(f"host: {host} not found")
except FileNotFoundError:
return "Cannot find host file."
sys.exit(1)
return hosts
def read_configs(filename, header, value):
"""
Authenticate via config.ini file
:return config item string
"""
config = configparser.ConfigParser()
config.read(filename)
value = config[header][value]
return value
def get_vuln_reports(base_url, uname, passw, hosts):
"""
get vuln results for a list of asset IDs and write them to excel
it always needs an excel sheet...
"""
# Create Excel sheet; will overwrite if same name
filename = 'VulnerabilityReport.xlsx'
workbook = xlsxwriter.Workbook(filename)
# Define headers for each sheet
headers = [ 'Title', 'CVSS Score', 'Risk Score', 'Malware', 'Exploits',
'Instances', 'Exceptions', 'First Found', 'Reintroduced',
'Solution' ]
# Iterate the Asset IDs and export the vulnerabilities
for host_id, host in hosts.items():
url = f'{base_url}api/3/assets/{host_id}/vulnerabilities'
try:
response = requests.get(url,
auth=HTTPBasicAuth(uname, passw), verify=False)
except requests.exceptions.RequestException as e:
print(f"An error occurred: {e}")
sys.exit(1)
result = response.json()
# Create the new sheet within naming params
row = 0
col = 0
# each sheet is the name of a host
sheetname = sanitize_name(host)
worksheet = workbook.add_worksheet(sheetname)
# Write out the header row
for header in headers:
worksheet.write(row, col, header)
col += 1
for vuln in result["resources"]:
# fetch additional data on the vuln
vurl = f'{base_url}api/3/vulnerabilities/{vuln["id"]}'
try:
vresp = requests.get(vurl, auth=HTTPBasicAuth(uname,
passw), verify=False)
except requests.exceptions.RequestException as e:
print(f"An error occurred: {e}")
sys.exit(1)
vres = vresp.json()
# Reset the column each time we iterate a vuln
row += 1
col = 0
# Write vulnerability title
worksheet.write(row, col, vuln["id"])
col += 1
# Write CVSS Score
cvss = vres["cvss"]["v2"]["score"]
# Add workbook format for color fill
cell_format = workbook.add_format()
# Define severity of CVSS score
# is critical
if is_between(cvss, 9.0, 10.0):
print(f'critical: {cvss}')
cell_format.set_bg_color('red')
worksheet.write(row, col, cvss, cell_format)
# is high
elif is_between(cvss, 7.0, 8.9):
print(f'high: {cvss}')
cell_format.set_bg_color('orange')
worksheet.write(row, col, cvss, cell_format)
# is medium
elif is_between(cvss, 4.0, 6.9):
print(f'medium: {cvss}')
cell_format.set_bg_color('yellow')
worksheet.write(row, col, cvss, cell_format)
# is low
elif is_between(cvss, 0.1 - 3.9):
print(f'low: {cvss}')
cell_format.set_bg_color('green')
worksheet.write(row, col, cvss, cell_format)
# likely zero
else:
worksheet.write(row, col, cvss)
col += 1
# Write Risk Score
worksheet.write(row, col, vres["riskScore"])
col += 1
# Write Malware
worksheet.write(row, col, vres["malwareKits"])
col += 1
# Write Exploits
worksheet.write(row, col, vres["exploits"])
col += 1
# Write Instances
worksheet.write(row, col, vuln["instances"])
col += 1
# Write Exceptions
# some results are conditional; will except if not found
if 'exceptions' in vuln["results"][0]:
worksheet.write(row, col, vuln["results"][0]["exceptions"][0])
else:
# else, enter 0
worksheet.write(row, col, 0)
col += 1
# Write First Found date
worksheet.write(row, col, vuln["since"])
col += 1
# Write Reintroduced date
if 'reintroduced' in vuln["results"][0]:
worksheet.write(row, col, vuln["results"][0]["reintroduced"])
col += 1
# Write Solutions
for link in vres["links"]:
if link["rel"] == 'Vulnerability Solutions':
worksheet.write(row, col, link["href"])
# autofit cells
worksheet.autofit()
workbook.close()
print(f"Results exported to: {filename}")
def main(f):
"""
main calls
"""
# path to config.ini
config_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'config.ini'))
#API v3 user/pass keys for base64 auth
passw = read_configs(config_path, 'IVM', 'R7_IVM_PASSW')
uname = read_configs(config_path, 'IVM', 'R7_IVM_USER')
base_url = read_configs(config_path, 'IVM', 'IVM_URL')
# Host file from the script argument
hostfile = f
# Get the list of hosts as IVM host IDs
print("Fetching asset IDs for vulns...")
hosts = get_host_ids(base_url, uname, passw, hostfile)
# get vuln reports for a list of hostnames
print("Exporting to spreadsheet...")
print("Have you considered a better tool than excel?")
get_vuln_reports(base_url, uname, passw, hosts)
print("Complete!")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Fetch vuln reports in IVM for a list of hosts"
)
parser.add_argument("-f", required=True, type=str,
help="The file with the list of hostnames")
args = parser.parse_args()
f = args.f
main(f)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment