Skip to content

Commit

Permalink
Added Export, Patching Prioritization System & Multiple CVE Support
Browse files Browse the repository at this point in the history
  • Loading branch information
xaitax committed Jan 15, 2024
1 parent 07389f0 commit 8a9841c
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 50 deletions.
48 changes: 46 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,60 @@ SploitScan is a powerful and user-friendly tool designed to streamline the proce
- **EPSS Integration**: Includes Exploit Prediction Scoring System (EPSS) data, offering a probability score for the likelihood of CVE exploitation, aiding in prioritization.
- **PoC Exploits Aggregation**: Gathers publicly available PoC exploits, enhancing the understanding of vulnerabilities.
- **CISA KEV**: Shows if the CVE has been listed in the Known Exploited Vulnerabilities (KEV) of CISA.
- **Patching Priority System**: Evaluates and assigns a priority rating for patching based on various factors including public exploits availability.
- **Multi-CVE Support and Export Options**: Supports multiple CVEs in a single run and allows exporting the results to JSON and CSV formats.
- **User-Friendly Interface**: Easy to use, providing clear and concise information.
- **Comprehensive Security Tool**: Ideal for quick security assessments and staying informed about recent vulnerabilities.

## 🚀 Usage



<hr>

**Regular**:

```bash
python sploitscan.py CVE-YYYY-NNNNN
```

**Enter one or more CVE IDs to fetch data. Separate multiple CVE IDs with spaces.**

```bash
python sploitscan.py CVE-YYYY-NNNNN CVE-YYYY-NNNNN
```

**Optional: Export the results to a JSON or CSV file. Specify the format: 'json' or 'csv'.**

```bash
python cve_poc.py CVE-YYYY-NNNNN
python sploitscan.py CVE-YYYY-NNNNN -e JSON
```

<img width="862" alt="image" src="https://github.com/xaitax/SploitScan/assets/5014849/5414a8a8-67ae-4fcf-b3b3-3c5003892e08">
## 🛡️ Patching Prioritization System

The Patching Prioritization System in SploitScan provides a strategic approach to prioritizing security patches based on the severity and exploitability of vulnerabilities. It's influenced by the model from [CVE Prioritizer](https://github.com/TURROKS/CVE_Prioritizer), with enhancements for handling publicly available exploits. Here's how it works:

- A+ Priority: Assigned to CVEs listed in CISA's KEV or those with publicly available exploits. This reflects the highest risk and urgency for patching.
- A to D Priority: Based on a combination of CVSS scores and EPSS probability percentages. The decision matrix is as follows:
- A: CVSS score >= 6.0 and EPSS score >= 0.2. High severity with a significant probability of exploitation.
- B: CVSS score >= 6.0 but EPSS score < 0.2. High severity but lower probability of exploitation.
- C: CVSS score < 6.0 and EPSS score >= 0.2. Lower severity but higher probability of exploitation.
- D: CVSS score < 6.0 and EPSS score < 0.2. Lower severity and lower probability of exploitation.

This system assists users in making informed decisions on which vulnerabilities to patch first, considering both their potential impact and the likelihood of exploitation. Thresholds can be changed to your business needs.

## 📆 Changelog

### [15th January 2024] - Enhancement Update

- **Multiple CVE Support**: Now capable of handling multiple CVE IDs in a single execution.
- **JSON and CSV Export**: Added functionality to export results to JSON and CSV files.
- **Enhanced CVE Display**: Improved visual differentiation and information layout for each CVE.
- **Patching Priority System**: Introduced a priority rating system for patching, influenced by various factors including the availability of public exploits.

### [13th January 2024] - Initial Release

- Initial release of SploitScan.

## Contributing
Contributions are welcome. Please feel free to fork, modify, and make pull requests or report issues.
Expand Down
191 changes: 143 additions & 48 deletions sploitscan.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,63 @@
#!/usr/bin/env python3

import requests
import argparse
import datetime
import json
import csv
import re
from tabulate import tabulate

BLUE = "\033[94m"
GREEN = "\033[92m"
ENDC = "\033[0m"

NVD_API_URL = "https://services.nvd.nist.gov/rest/json/cves/2.0?cveId={cve_id}"
EPSS_API_URL = "https://api.first.org/data/v1/epss?cve={cve_id}"
CISA_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
POC_API_URL = "https://poc-in-github.motikan2010.net/api/v1/"

CVSS_THRESHOLD = 6.0
EPSS_THRESHOLD = 0.2

PRIORITY_COLORS = {
"A+": "\033[91m",
"A": "\033[31m",
"B": "\033[93m",
"C": "\033[94m",
"D": "\033[92m",
}


def calculate_priority(cve_id, nvd_data, epss_data, poc_data, cisa_data):
cvss_score = float(
nvd_data["vulnerabilities"][0]["cve"]["metrics"]["cvssMetricV31"][0][
"cvssData"
]["baseScore"]
)
epss_score = (
float(epss_data["data"][0]["epss"])
if epss_data and "data" in epss_data and epss_data["data"]
else 0
)
in_cisa_kev = any(
vuln["cveID"] == cve_id for vuln in cisa_data.get("vulnerabilities", [])
)
has_public_exploits = len(poc_data.get("pocs", [])) > 0

if in_cisa_kev or has_public_exploits:
priority = "A+"
elif cvss_score >= CVSS_THRESHOLD and epss_score >= EPSS_THRESHOLD:
priority = "A"
elif cvss_score >= CVSS_THRESHOLD and epss_score < EPSS_THRESHOLD:
priority = "B"
elif cvss_score < CVSS_THRESHOLD and epss_score >= EPSS_THRESHOLD:
priority = "C"
else:
priority = "D"

return priority


def fetch_nvd_data(cve_id):
nvd_url = NVD_API_URL.format(cve_id=cve_id)
Expand All @@ -19,7 +66,7 @@ def fetch_nvd_data(cve_id):
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"[❌] Error fetching data from NVD: {e}")
print(f" Error fetching data from NVD: {e}")


def display_nvd_data(cve_data):
Expand Down Expand Up @@ -62,7 +109,7 @@ def display_nvd_data(cve_data):
f"\n{description_label} {description}\n{published_label} {published}\n{base_score_label} {baseScore}\n{base_severity_label} {baseSeverity}\n"
)
else:
print("\n[❌] No NVD data found for this CVE ID.\n")
print("\n No NVD data found for this CVE ID.\n")


def fetch_epss_score(cve_id):
Expand All @@ -71,22 +118,21 @@ def fetch_epss_score(cve_id):
response = requests.get(epss_url)
response.raise_for_status()
epss_data = response.json()
if epss_data and "data" in epss_data and len(epss_data["data"]) > 0:
epss_score = epss_data["data"][0].get("epss", "N/A")
return float(epss_score)
return "N/A"
return epss_data # Return the entire data
except requests.exceptions.RequestException as e:
print(f"[❌] Error fetching EPSS data: {e}")
return "N/A"
print(f" Error fetching EPSS data: {e}")
return None


def display_epss_score(epss_score):
if epss_score != "N/A":
print(
f"EPSS Score: {float(epss_score) * 100:.2f}% Probability of exploitation in the wild (following publication).\n"
)
def display_epss_score(epss_data):
if epss_data and "data" in epss_data and len(epss_data["data"]) > 0:
epss_score = epss_data["data"][0].get("epss", "N/A")
if epss_score != "N/A":
print(
f"EPSS Score: {float(epss_score) * 100:.2f}% Probability of exploitation in the wild (following publication).\n"
)
else:
print("[❌] No EPSS data found for this CVE ID.\n")
print(" No EPSS data found for this CVE ID.\n")


def fetch_cisa_data():
Expand All @@ -95,7 +141,7 @@ def fetch_cisa_data():
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"[❌] Error fetching data from CISA: {e}")
print(f" Error fetching data from CISA: {e}")


def display_cisa_status(cve_id, cisa_data):
Expand All @@ -115,7 +161,7 @@ def fetch_poc_data(base_url, params=None):
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"[❌] An error occurred fetching PoC data: {e}")
print(f" An error occurred fetching PoC data: {e}")


def display_poc_data(data):
Expand All @@ -138,15 +184,30 @@ def display_poc_data(data):
]
table.append(row)

print(tabulate(table, headers=headers, tablefmt="fancy_grid"))
print(tabulate(table, headers=headers, tablefmt="fancy_grid") + "\n")
else:
print("No PoC data found.")
print("No PoC data found.\n")


def is_valid_cve_id(cve_id):
return re.match(r"CVE-\d{4}-\d{4,7}$", cve_id) is not None


def export_to_json(all_results, filename):
with open(filename, "w") as file:
json.dump(all_results, file, indent=4)
print(BLUE + f"\n✅ Data exported to JSON file: {filename}" + ENDC + "\n")


def export_to_csv(data, filename):
with open(filename, "w", newline="") as file:
writer = csv.writer(file)
writer.writerow(data[0].keys())
for row in data:
writer.writerow(row.values())
print(BLUE + f"\n✅ Data exported to CSV file: {filename}" + ENDC + "\n")


def display_banner():
banner = """
███████╗██████╗ ██╗ ██████╗ ██╗████████╗███████╗ ██████╗ █████╗ ███╗ ██╗
Expand All @@ -160,50 +221,84 @@ def display_banner():
print("Alexander Hagenah / @xaitax / [email protected]\n")


def main(cve_id):
if not cve_id:
print(
"[❌] No CVE ID provided. Please provide a CVE ID in the format CVE-YYYY-NNNNN."
)
return
def main(cve_ids, export_format=None):
all_results = []
for cve_id in cve_ids:
cve_result = {"CVE ID": cve_id}

if not is_valid_cve_id(cve_id):
print(
"[❌] Invalid CVE ID format. Please provide a CVE ID in the format CVE-YYYY-NNNNN."
header = f" CVE ID: {cve_id} "
print(GREEN + "=" * len(header) + ENDC)
print(GREEN + header + ENDC)
print(GREEN + "=" * len(header) + ENDC + "\n")

if not cve_id:
print(
"❌ No CVE ID provided. Please provide a CVE ID in the format CVE-YYYY-NNNNN."
)
continue

if not is_valid_cve_id(cve_id):
print(
"❌ Invalid CVE ID format. Please provide a CVE ID in the format CVE-YYYY-NNNNN."
)
continue

print(BLUE + f"🔍 Fetching vulnerability information:" + ENDC)
nvd_data = fetch_nvd_data(cve_id)
display_nvd_data(nvd_data)

print(BLUE + f"💥 Fetching Exploit Prediction Score (EPSS):\n" + ENDC)
epss_data = fetch_epss_score(cve_id)
display_epss_score(epss_data)

print(BLUE + f"🛡️ Fetching CISA Catalog of Known Exploited Vulnerabilities:\n" + ENDC)
cisa_data = fetch_cisa_data()
display_cisa_status(cve_id, cisa_data)

print(BLUE + f"\n💣 Fetching public exploits / PoC: \n" + ENDC)
poc_data = fetch_poc_data(
POC_API_URL, params={"cve_id": cve_id, "sort": "stargazers_count"}
)
return
display_poc_data(poc_data)

print(BLUE + f"🔍 Fetching vulnerability information for {cve_id}." + ENDC)
nvd_data = fetch_nvd_data(cve_id)
display_nvd_data(nvd_data)
relevant_cisa_data = next((item for item in cisa_data.get("vulnerabilities", []) if item["cveID"] == cve_id), None)

print(BLUE + f"💥 Fetching Exploit Prediction Score (EPSS) for {cve_id}.\n" + ENDC)
epss_score = fetch_epss_score(cve_id)
display_epss_score(epss_score)
cve_result["NVD_Data"] = nvd_data if nvd_data else {}
cve_result["EPSS_Data"] = epss_data if epss_data else {}
cve_result["CISA_Data"] = relevant_cisa_data if relevant_cisa_data else {}
cve_result["PoC_Data"] = poc_data if poc_data else {}

print(BLUE + f"🛡️ Fetching CISA Catalog of Known Exploited Vulnerabilities for {cve_id}.\n" + ENDC)
cisa_data = fetch_cisa_data()
display_cisa_status(cve_id, cisa_data)
priority = calculate_priority(cve_id, nvd_data, epss_data, poc_data, cisa_data)
priority_color = PRIORITY_COLORS.get(priority, ENDC)
print(BLUE + f"⚠️ Patching Priority Rating: {priority_color}{priority}{ENDC}\n")
cve_result["Priority"] = {"priority": priority}

print(BLUE + f"\n💣 Fetching public exploits / PoC for {cve_id}.\n" + ENDC)
poc_data = fetch_poc_data(
POC_API_URL, params={"cve_id": cve_id, "sort": "stargazers_count"}
)
display_poc_data(poc_data)
all_results.append(cve_result)

if export_format == "json":
export_to_json(all_results, f"{'_'.join(cve_ids)}_export.json")
elif export_format == "csv":
export_to_csv(all_results, f"{'_'.join(cve_ids)}_export.csv")


if __name__ == "__main__":
display_banner()
parser = argparse.ArgumentParser(
description="SploitScan: Fetch and display data from NVD and public exploits for a given CVE ID."
description="SploitScan: Fetch and display data from NVD and public exploits for given CVE IDs."
)
parser.add_argument(
"cve_id",
"cve_ids",
type=str,
nargs="?",
default="",
help="The CVE ID for which to fetch data. Format: CVE-YYYY-NNNNN (Example: CVE-2023-23397)",
nargs="+",
help="Enter one or more CVE IDs to fetch data. Separate multiple CVE IDs with spaces. Format for each ID: CVE-YYYY-NNNNN (Example: CVE-2023-23397 CVE-2024-12345)",
)
parser.add_argument(
"-e",
"--export",
choices=["json", "csv"],
help="Optional: Export the results to a JSON or CSV file. Specify the format: 'json' or 'csv'.",
)

args = parser.parse_args()

main(args.cve_id)
main(args.cve_ids, args.export)

0 comments on commit 8a9841c

Please sign in to comment.