mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-11 21:54:56 +03:00
318 lines
12 KiB
Python
318 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Metasploit Vulnerability Validation Automation
|
|
|
|
Uses Metasploit RPC API (msfrpcd) to automate vulnerability validation
|
|
by running check commands against scan findings.
|
|
|
|
Requirements:
|
|
pip install requests pandas pymetasploit3
|
|
|
|
Usage:
|
|
python process.py validate --vulns vulns.csv --msf-host 127.0.0.1 --msf-pass password
|
|
python process.py report --results validation_results.csv
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
import time
|
|
from datetime import datetime
|
|
|
|
import pandas as pd
|
|
import requests
|
|
|
|
|
|
class MetasploitRPC:
|
|
"""Interface to Metasploit RPC API for automated vulnerability validation."""
|
|
|
|
def __init__(self, host: str = "127.0.0.1", port: int = 55553,
|
|
username: str = "msf", password: str = "password",
|
|
ssl: bool = True):
|
|
self.base_url = f"{'https' if ssl else 'http'}://{host}:{port}/api/"
|
|
self.token = None
|
|
self.session = requests.Session()
|
|
self.session.verify = False
|
|
|
|
# Authenticate
|
|
self._login(username, password)
|
|
|
|
def _login(self, username: str, password: str):
|
|
"""Authenticate to msfrpcd."""
|
|
result = self._call("auth.login", [username, password])
|
|
if result.get("result") == "success":
|
|
self.token = result["token"]
|
|
print(f"[+] Authenticated to Metasploit RPC")
|
|
else:
|
|
raise ConnectionError(f"Metasploit RPC auth failed: {result}")
|
|
|
|
def _call(self, method: str, params: list = None) -> dict:
|
|
"""Make an RPC call to Metasploit."""
|
|
payload = json.dumps({
|
|
"jsonrpc": "2.0",
|
|
"method": method,
|
|
"id": 1,
|
|
"params": [self.token] + (params or []) if self.token else (params or [])
|
|
})
|
|
|
|
try:
|
|
response = self.session.post(
|
|
self.base_url, data=payload,
|
|
headers={"Content-Type": "application/json"},
|
|
timeout=60
|
|
)
|
|
return response.json().get("result", response.json())
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
def create_console(self) -> str:
|
|
"""Create a new Metasploit console."""
|
|
result = self._call("console.create")
|
|
console_id = result.get("id")
|
|
print(f"[+] Console created: {console_id}")
|
|
return console_id
|
|
|
|
def console_write(self, console_id: str, command: str):
|
|
"""Write a command to a console."""
|
|
self._call("console.write", [console_id, command + "\n"])
|
|
|
|
def console_read(self, console_id: str, timeout: int = 30) -> str:
|
|
"""Read output from a console with polling."""
|
|
output = ""
|
|
start = time.time()
|
|
while time.time() - start < timeout:
|
|
result = self._call("console.read", [console_id])
|
|
data = result.get("data", "")
|
|
output += data
|
|
if not result.get("busy", True):
|
|
break
|
|
time.sleep(2)
|
|
return output
|
|
|
|
def run_check(self, console_id: str, module: str, rhosts: str,
|
|
options: dict = None) -> dict:
|
|
"""Run a module check command and return results."""
|
|
self.console_write(console_id, f"use {module}")
|
|
time.sleep(1)
|
|
self.console_read(console_id, timeout=5)
|
|
|
|
self.console_write(console_id, f"set RHOSTS {rhosts}")
|
|
time.sleep(0.5)
|
|
|
|
if options:
|
|
for key, value in options.items():
|
|
self.console_write(console_id, f"set {key} {value}")
|
|
time.sleep(0.5)
|
|
|
|
self.console_read(console_id, timeout=5)
|
|
self.console_write(console_id, "check")
|
|
output = self.console_read(console_id, timeout=60)
|
|
|
|
is_vulnerable = any(
|
|
indicator in output.lower()
|
|
for indicator in ["is vulnerable", "appears vulnerable", "[+]"]
|
|
)
|
|
is_not_vulnerable = any(
|
|
indicator in output.lower()
|
|
for indicator in ["not vulnerable", "does not appear", "safe"]
|
|
)
|
|
|
|
status = "unknown"
|
|
if is_vulnerable and not is_not_vulnerable:
|
|
status = "vulnerable"
|
|
elif is_not_vulnerable:
|
|
status = "not_vulnerable"
|
|
elif "check is not supported" in output.lower():
|
|
status = "check_unsupported"
|
|
|
|
return {
|
|
"module": module,
|
|
"target": rhosts,
|
|
"status": status,
|
|
"output": output[:2000],
|
|
"timestamp": datetime.now().isoformat(),
|
|
}
|
|
|
|
def search_module(self, console_id: str, search_term: str) -> list:
|
|
"""Search for Metasploit modules."""
|
|
self.console_write(console_id, f"search {search_term}")
|
|
output = self.console_read(console_id, timeout=30)
|
|
|
|
modules = []
|
|
for line in output.split("\n"):
|
|
if line.strip().startswith(("exploit/", "auxiliary/")):
|
|
parts = line.strip().split()
|
|
if len(parts) >= 3:
|
|
modules.append({
|
|
"module": parts[0],
|
|
"date": parts[1],
|
|
"rank": parts[2] if len(parts) > 2 else "",
|
|
"description": " ".join(parts[3:]) if len(parts) > 3 else "",
|
|
})
|
|
return modules
|
|
|
|
def destroy_console(self, console_id: str):
|
|
"""Destroy a console."""
|
|
self._call("console.destroy", [console_id])
|
|
|
|
|
|
class VulnerabilityValidator:
|
|
"""Validate scanner findings using Metasploit check capabilities."""
|
|
|
|
# Map common CVEs to Metasploit modules
|
|
CVE_MODULE_MAP = {
|
|
"CVE-2017-0144": "exploit/windows/smb/ms17_010_eternalblue",
|
|
"CVE-2019-0708": "exploit/windows/rdp/cve_2019_0708_bluekeep_rce",
|
|
"CVE-2020-1472": "exploit/windows/dcerpc/cve_2020_1472_zerologon",
|
|
"CVE-2021-44228": "exploit/multi/http/log4shell_header_injection",
|
|
"CVE-2021-34527": "exploit/windows/dcerpc/cve_2021_1675_printnightmare",
|
|
"CVE-2022-26134": "exploit/multi/http/atlassian_confluence_namespace_ognl",
|
|
"CVE-2023-27997": "exploit/linux/http/fortinet_fortigate_sslvpn_rce",
|
|
"CVE-2024-3094": "auxiliary/scanner/ssh/xz_backdoor_scanner",
|
|
"CVE-2014-0160": "auxiliary/scanner/ssl/openssl_heartbleed",
|
|
"CVE-2014-6271": "exploit/multi/http/apache_mod_cgi_bash_env_exec",
|
|
}
|
|
|
|
# Map service/plugin families to auxiliary scanner modules
|
|
SERVICE_SCANNER_MAP = {
|
|
"smb": "auxiliary/scanner/smb/smb_ms17_010",
|
|
"rdp": "auxiliary/scanner/rdp/cve_2019_0708_bluekeep",
|
|
"ssl_heartbleed": "auxiliary/scanner/ssl/openssl_heartbleed",
|
|
"http_dir_listing": "auxiliary/scanner/http/dir_listing",
|
|
"ftp_anonymous": "auxiliary/scanner/ftp/anonymous",
|
|
"ssh_enumusers": "auxiliary/scanner/ssh/ssh_enumusers",
|
|
"mssql_login": "auxiliary/scanner/mssql/mssql_login",
|
|
}
|
|
|
|
def __init__(self):
|
|
self.results = []
|
|
|
|
def find_module(self, cve: str) -> str:
|
|
"""Find the best Metasploit module for a given CVE."""
|
|
return self.CVE_MODULE_MAP.get(cve, "")
|
|
|
|
def validate_from_csv(self, csv_path: str, msf: MetasploitRPC) -> pd.DataFrame:
|
|
"""Validate vulnerabilities from a CSV file using Metasploit."""
|
|
df = pd.read_csv(csv_path)
|
|
console_id = msf.create_console()
|
|
|
|
try:
|
|
for _, row in df.iterrows():
|
|
cve = row.get("cve", "")
|
|
host = row.get("host", row.get("hostname", ""))
|
|
module = row.get("msf_module", self.find_module(cve))
|
|
|
|
if not module:
|
|
self.results.append({
|
|
"cve": cve, "host": host, "module": "",
|
|
"status": "no_module", "output": "No Metasploit module mapped",
|
|
"timestamp": datetime.now().isoformat(),
|
|
})
|
|
print(f" [?] {cve} on {host}: No module available")
|
|
continue
|
|
|
|
print(f" [*] Checking {cve} on {host} with {module}...")
|
|
result = msf.run_check(console_id, module, host)
|
|
result["cve"] = cve
|
|
self.results.append(result)
|
|
|
|
status_icon = "[+]" if result["status"] == "vulnerable" else "[-]"
|
|
print(f" {status_icon} {cve} on {host}: {result['status']}")
|
|
|
|
finally:
|
|
msf.destroy_console(console_id)
|
|
|
|
return pd.DataFrame(self.results)
|
|
|
|
def generate_report(self, output_path: str):
|
|
"""Generate validation report."""
|
|
if not self.results:
|
|
print("[-] No results to report")
|
|
return
|
|
|
|
df = pd.DataFrame(self.results)
|
|
total = len(df)
|
|
confirmed = len(df[df["status"] == "vulnerable"])
|
|
not_vuln = len(df[df["status"] == "not_vulnerable"])
|
|
unknown = len(df[df["status"].isin(["unknown", "check_unsupported"])])
|
|
no_module = len(df[df["status"] == "no_module"])
|
|
|
|
html = f"""<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Metasploit Validation Report</title>
|
|
<style>
|
|
body {{ font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }}
|
|
.header {{ background: #1a1a2e; color: white; padding: 20px; border-radius: 8px; }}
|
|
.metrics {{ display: flex; gap: 15px; margin: 20px 0; }}
|
|
.card {{ background: white; padding: 20px; border-radius: 8px; flex: 1; text-align: center;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1); }}
|
|
.card h3 {{ margin: 0; font-size: 2em; }}
|
|
.confirmed {{ border-top: 4px solid #e74c3c; }}
|
|
.safe {{ border-top: 4px solid #27ae60; }}
|
|
.unknown {{ border-top: 4px solid #f39c12; }}
|
|
table {{ width: 100%; border-collapse: collapse; background: white; margin: 15px 0; }}
|
|
th {{ background: #2c3e50; color: white; padding: 10px; text-align: left; }}
|
|
td {{ padding: 8px; border-bottom: 1px solid #eee; }}
|
|
.status-vulnerable {{ color: #e74c3c; font-weight: bold; }}
|
|
.status-not_vulnerable {{ color: #27ae60; }}
|
|
.status-unknown {{ color: #f39c12; }}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<h1>Vulnerability Validation Report</h1>
|
|
<p>Tool: Metasploit Framework | Date: {datetime.now().strftime('%Y-%m-%d')}</p>
|
|
</div>
|
|
<div class="metrics">
|
|
<div class="card confirmed"><h3>{confirmed}</h3><p>Confirmed Vulnerable</p></div>
|
|
<div class="card safe"><h3>{not_vuln}</h3><p>Not Vulnerable</p></div>
|
|
<div class="card unknown"><h3>{unknown}</h3><p>Inconclusive</p></div>
|
|
<div class="card"><h3>{no_module}</h3><p>No Module Available</p></div>
|
|
</div>
|
|
<h2>Validation Results</h2>
|
|
<table>
|
|
<tr><th>CVE</th><th>Host</th><th>Module</th><th>Status</th></tr>
|
|
{''.join(f'<tr><td>{r.get("cve","")}</td><td>{r.get("target",r.get("host",""))}</td><td>{r.get("module","")}</td><td class="status-{r["status"]}">{r["status"]}</td></tr>' for r in self.results)}
|
|
</table>
|
|
</body>
|
|
</html>"""
|
|
|
|
with open(output_path, "w", encoding="utf-8") as f:
|
|
f.write(html)
|
|
print(f"[+] Report saved to: {output_path}")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Metasploit Vulnerability Validation")
|
|
subparsers = parser.add_subparsers(dest="command")
|
|
|
|
val_p = subparsers.add_parser("validate", help="Validate vulnerabilities with Metasploit")
|
|
val_p.add_argument("--vulns", required=True, help="CSV with cve, host columns")
|
|
val_p.add_argument("--msf-host", default="127.0.0.1", help="msfrpcd host")
|
|
val_p.add_argument("--msf-port", type=int, default=55553, help="msfrpcd port")
|
|
val_p.add_argument("--msf-user", default="msf", help="msfrpcd username")
|
|
val_p.add_argument("--msf-pass", required=True, help="msfrpcd password")
|
|
val_p.add_argument("--output", default="validation_results.csv")
|
|
val_p.add_argument("--report", default="validation_report.html")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.command == "validate":
|
|
msf = MetasploitRPC(
|
|
host=args.msf_host, port=args.msf_port,
|
|
username=args.msf_user, password=args.msf_pass
|
|
)
|
|
validator = VulnerabilityValidator()
|
|
results_df = validator.validate_from_csv(args.vulns, msf)
|
|
results_df.to_csv(args.output, index=False)
|
|
print(f"[+] Results saved to: {args.output}")
|
|
validator.generate_report(args.report)
|
|
|
|
else:
|
|
parser.print_help()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|