This repository has been archived on 2026-09-11. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files

204 lines
6.1 KiB
Python

#!/usr/bin/env python3
"""CVE Scanner for WordPress plugins using Wordfence vulnerability database."""
import argparse
import csv
import json
import os
import sys
def load_json_local(path: str) -> dict:
"""Load JSON data from a local file.
Args:
path: Filesystem path to the JSON file.
Returns:
Parsed JSON data as a dictionary or list.
"""
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def load_json_from_url(url: str) -> dict:
"""Load JSON data from a URL.
Args:
url: URL to fetch JSON from.
Returns:
Parsed JSON data as a dictionary or list.
"""
import requests
r = requests.get(url, timeout=30)
r.raise_for_status()
return r.json()
def parse_args() -> argparse.Namespace:
"""Parse command-line arguments.
Returns:
Parsed arguments namespace.
"""
parser = argparse.ArgumentParser(
description="Scan WordPress plugins for CVE vulnerabilities using Wordfence data."
)
parser.add_argument(
"--input",
"-i",
help="Path to local vulnerabilities JSON file (default: wordfence_vulnerabilities.json)",
default="wordfence_vulnerabilities.json"
)
parser.add_argument(
"--url",
"-u",
help="URL to download vulnerabilities JSON if local file not found",
default=""
)
parser.add_argument(
"--plugins",
"-p",
nargs="+",
help="List of plugin slugs to check (space-separated)",
default=[]
)
parser.add_argument(
"--plugins-file",
"-f",
help="Path to file containing plugin slugs (one per line)",
default=None
)
parser.add_argument(
"--out-csv",
"-c",
help="Output CSV file path (if not specified, CSV output is skipped)",
default=None
)
parser.add_argument(
"--out-json",
"-j",
help="Output JSON file path (if not specified, prints to stdout)",
default=None
)
return parser.parse_args()
def load_plugin_list(plugins: list[str], plugins_file: str | None) -> set[str]:
"""Load and combine plugin slugs from CLI args and optional file.
Args:
plugins: List of plugin slugs from command line.
plugins_file: Optional path to file with plugin slugs (one per line).
Returns:
Set of plugin slugs to check.
"""
plugin_set = set(plugins)
if plugins_file and os.path.exists(plugins_file):
with open(plugins_file, "r", encoding="utf-8") as f:
for line in f:
slug = line.strip()
if slug and not slug.startswith("#"):
plugin_set.add(slug)
return plugin_set
def main():
"""Main entry point for the CVE scanner."""
args = parse_args()
# Load plugin list
bad_plugins = load_plugin_list(args.plugins, args.plugins_file)
if not bad_plugins:
print("Warning: No plugins specified to check. Use --plugins or --plugins-file.")
print("Proceeding to load vulnerability data anyway...")
# Load vulnerability data
data = None
# Prioritize URL if specified
if args.url:
print(f"Downloading from URL: {args.url}")
data = load_json_from_url(args.url)
elif os.path.exists(args.input):
print(f"Loading local file: {args.input}")
data = load_json_local(args.input)
else:
raise SystemExit(f"Error: Local file '{args.input}' not found and --url not provided.")
# Extract vulnerability entries
if isinstance(data, dict) and "vulnerabilities" in data:
entries = data["vulnerabilities"]
elif isinstance(data, list):
entries = data
else:
entries = []
if isinstance(data, dict):
for v in data.values():
if isinstance(v, list):
entries = v
break
# Find matches
matches = []
for entry in entries:
try:
if entry.get("type") != "plugin":
continue
slug = entry.get("slug", "").strip()
if not slug:
continue
if slug in bad_plugins:
matches.append({
"slug": slug,
"title": entry.get("title"),
"cve": entry.get("cve"),
"severity": entry.get("severity"),
"cvss_score": entry.get("cvss_score"),
"cve_link": entry.get("cve_link"),
"raw": entry
})
except Exception:
continue
# Display results
if not matches:
print("No matches found for the specified plugins.")
else:
print(f"\nFound {len(matches)} vulnerability matches:")
for m in matches:
print(f"- {m['slug']} | CVE: {m.get('cve')} | sev: {m.get('severity')} | CVSS: {m.get('cvss_score')}")
print(f" link: {m.get('cve_link')}")
# Write CSV output (only if specified)
if args.out_csv:
with open(args.out_csv, "w", newline='', encoding="utf-8") as csvf:
writer = csv.DictWriter(csvf, fieldnames=["slug", "title", "cve", "severity", "cvss_score", "cve_link"])
writer.writeheader()
for m in matches:
writer.writerow({
"slug": m["slug"],
"title": m.get("title"),
"cve": m.get("cve"),
"severity": m.get("severity"),
"cvss_score": m.get("cvss_score"),
"cve_link": m.get("cve_link")
})
print(f"\nSaved CSV results to {args.out_csv}")
# Write JSON output (to file or stdout)
if args.out_json:
with open(args.out_json, "w", encoding="utf-8") as jf:
json.dump(matches, jf, ensure_ascii=False, indent=2)
print(f"Saved JSON results to {args.out_json}")
else:
# Print to stdout if no JSON file specified
print("\nJSON output:")
print(json.dumps(matches, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()