#!/usr/bin/env python3 """ GoPhish Campaign Automation and Analytics Automates phishing simulation campaigns via the GoPhish REST API. Creates campaigns, monitors progress, and generates detailed analytics reports. Usage: python process.py create --config campaign.json python process.py status --campaign-id 1 python process.py report --campaign-id 1 --output report.html python process.py list """ import argparse import json import sys import csv import os from datetime import datetime, timezone from pathlib import Path from dataclasses import dataclass, field from typing import Optional from collections import defaultdict try: import requests HAS_REQUESTS = True except ImportError: HAS_REQUESTS = False GOPHISH_API_URL = os.environ.get("GOPHISH_API_URL", "https://localhost:3333") GOPHISH_API_KEY = os.environ.get("GOPHISH_API_KEY", "") class GoPhishClient: """Client for interacting with the GoPhish REST API.""" def __init__(self, api_url: str, api_key: str, verify_ssl: bool = False): self.api_url = api_url.rstrip("/") self.api_key = api_key self.verify_ssl = verify_ssl self.session = requests.Session() self.session.headers.update({ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" }) self.session.verify = verify_ssl def _get(self, endpoint: str) -> dict: resp = self.session.get(f"{self.api_url}{endpoint}") resp.raise_for_status() return resp.json() def _post(self, endpoint: str, data: dict) -> dict: resp = self.session.post(f"{self.api_url}{endpoint}", json=data) resp.raise_for_status() return resp.json() def _delete(self, endpoint: str) -> bool: resp = self.session.delete(f"{self.api_url}{endpoint}") resp.raise_for_status() return resp.status_code == 200 # Sending Profiles def create_sending_profile(self, name: str, host: str, from_address: str, username: str = "", password: str = "", ignore_cert: bool = False) -> dict: data = { "name": name, "host": host, "from_address": from_address, "username": username, "password": password, "ignore_cert_errors": ignore_cert } return self._post("/api/smtp/", data) def list_sending_profiles(self) -> list: return self._get("/api/smtp/") # Email Templates def create_template(self, name: str, subject: str, html: str, text: str = "", attachments: list = None) -> dict: data = { "name": name, "subject": subject, "html": html, "text": text, "attachments": attachments or [] } return self._post("/api/templates/", data) def import_email(self, raw_email: str, convert_links: bool = True) -> dict: data = { "content": raw_email, "convert_links": convert_links } return self._post("/api/import/email", data) def list_templates(self) -> list: return self._get("/api/templates/") # Landing Pages def create_page(self, name: str, html: str, capture_credentials: bool = True, capture_passwords: bool = False, redirect_url: str = "") -> dict: data = { "name": name, "html": html, "capture_credentials": capture_credentials, "capture_passwords": capture_passwords, "redirect_url": redirect_url } return self._post("/api/pages/", data) def import_site(self, url: str) -> dict: data = {"url": url, "include_resources": False} return self._post("/api/import/site", data) def list_pages(self) -> list: return self._get("/api/pages/") # User Groups def create_group(self, name: str, targets: list) -> dict: data = { "name": name, "targets": targets } return self._post("/api/groups/", data) def import_group_csv(self, name: str, csv_path: str) -> dict: targets = [] with open(csv_path, "r", encoding="utf-8") as f: reader = csv.DictReader(f) for row in reader: target = { "first_name": row.get("First Name", row.get("first_name", "")), "last_name": row.get("Last Name", row.get("last_name", "")), "email": row.get("Email", row.get("email", "")), "position": row.get("Position", row.get("position", "")) } if target["email"]: targets.append(target) return self.create_group(name, targets) def list_groups(self) -> list: return self._get("/api/groups/") # Campaigns def create_campaign(self, name: str, template_name: str, page_name: str, smtp_name: str, group_names: list, url: str, launch_date: str = "", send_by_date: str = "") -> dict: templates = self.list_templates() template = next((t for t in templates if t["name"] == template_name), None) pages = self.list_pages() page = next((p for p in pages if p["name"] == page_name), None) smtps = self.list_sending_profiles() smtp = next((s for s in smtps if s["name"] == smtp_name), None) groups_list = self.list_groups() groups = [g for g in groups_list if g["name"] in group_names] if not all([template, page, smtp, groups]): missing = [] if not template: missing.append(f"template '{template_name}'") if not page: missing.append(f"page '{page_name}'") if not smtp: missing.append(f"smtp '{smtp_name}'") if not groups: missing.append(f"groups {group_names}") raise ValueError(f"Missing components: {', '.join(missing)}") data = { "name": name, "template": {"name": template_name}, "page": {"name": page_name}, "smtp": {"name": smtp_name}, "groups": [{"name": g["name"]} for g in groups], "url": url } if launch_date: data["launch_date"] = launch_date if send_by_date: data["send_by_date"] = send_by_date return self._post("/api/campaigns/", data) def get_campaign(self, campaign_id: int) -> dict: return self._get(f"/api/campaigns/{campaign_id}") def get_campaign_summary(self, campaign_id: int) -> dict: return self._get(f"/api/campaigns/{campaign_id}/summary") def get_campaign_results(self, campaign_id: int) -> dict: return self._get(f"/api/campaigns/{campaign_id}/results") def list_campaigns(self) -> list: return self._get("/api/campaigns/") def complete_campaign(self, campaign_id: int) -> dict: return self._get(f"/api/campaigns/{campaign_id}/complete") def calculate_campaign_metrics(results: dict) -> dict: """Calculate detailed campaign metrics from GoPhish results.""" timeline = results.get("timeline", []) total_targets = len(results.get("results", [])) events = defaultdict(set) for event in timeline: email = event.get("email", "") message = event.get("message", "") if "Email Sent" in message: events["sent"].add(email) elif "Email Opened" in message: events["opened"].add(email) elif "Clicked Link" in message: events["clicked"].add(email) elif "Submitted Data" in message: events["submitted"].add(email) elif "Email Reported" in message: events["reported"].add(email) sent = len(events["sent"]) opened = len(events["opened"]) clicked = len(events["clicked"]) submitted = len(events["submitted"]) reported = len(events["reported"]) metrics = { "total_targets": total_targets, "emails_sent": sent, "emails_opened": opened, "links_clicked": clicked, "data_submitted": submitted, "emails_reported": reported, "open_rate": round(opened / max(sent, 1) * 100, 1), "click_rate": round(clicked / max(sent, 1) * 100, 1), "submit_rate": round(submitted / max(sent, 1) * 100, 1), "report_rate": round(reported / max(sent, 1) * 100, 1), "click_to_submit_rate": round(submitted / max(clicked, 1) * 100, 1), "resilience_score": round((1 - submitted / max(sent, 1)) * 100, 1), } # Department breakdown dept_stats = defaultdict(lambda: {"sent": 0, "opened": 0, "clicked": 0, "submitted": 0}) for result in results.get("results", []): dept = result.get("position", "Unknown") email = result.get("email", "") dept_stats[dept]["sent"] += 1 if email in events["opened"]: dept_stats[dept]["opened"] += 1 if email in events["clicked"]: dept_stats[dept]["clicked"] += 1 if email in events["submitted"]: dept_stats[dept]["submitted"] += 1 metrics["department_breakdown"] = dict(dept_stats) return metrics def generate_html_report(campaign: dict, metrics: dict) -> str: """Generate an HTML campaign report.""" name = campaign.get("name", "Unknown Campaign") created = campaign.get("created_date", "") status = campaign.get("status", "") html = f""" Phishing Simulation Report: {name}

Phishing Simulation Report

Campaign: {name}
Date: {created}
Status: {status}
Generated: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}

Campaign Metrics

{metrics['emails_sent']}
Emails Sent
{metrics['open_rate']}%
Open Rate
{metrics['click_rate']}%
Click Rate
{metrics['submit_rate']}%
Submit Rate
{metrics['report_rate']}%
Report Rate
{metrics['resilience_score']}%
Resilience Score

Funnel Analysis

StageCountRateVisual
Emails Sent{metrics['emails_sent']}100%
Emails Opened{metrics['emails_opened']}{metrics['open_rate']}%
Links Clicked{metrics['links_clicked']}{metrics['click_rate']}%
Data Submitted{metrics['data_submitted']}{metrics['submit_rate']}%
Emails Reported{metrics['emails_reported']}{metrics['report_rate']}%

Department Breakdown

""" for dept, stats in sorted(metrics.get("department_breakdown", {}).items()): dept_click_rate = round(stats["clicked"] / max(stats["sent"], 1) * 100, 1) html += f""" """ html += f"""
DepartmentSentOpenedClickedSubmittedClick Rate
{dept}{stats['sent']}{stats['opened']} {stats['clicked']}{stats['submitted']}{dept_click_rate}%

Industry Benchmarks

MetricYour ResultIndustry AverageTarget
Click Rate{metrics['click_rate']}%11-15%<5%
Submit Rate{metrics['submit_rate']}%3-5%<2%
Report Rate{metrics['report_rate']}%10-15%>70%

Recommendations

""" return html def generate_text_report(campaign: dict, metrics: dict) -> str: """Generate a text-based campaign report.""" name = campaign.get("name", "Unknown") lines = [] lines.append("=" * 60) lines.append(" PHISHING SIMULATION CAMPAIGN REPORT") lines.append("=" * 60) lines.append(f" Campaign: {name}") lines.append(f" Status: {campaign.get('status', '')}") lines.append(f" Created: {campaign.get('created_date', '')}") lines.append("") lines.append("[METRICS]") lines.append(f" Emails Sent: {metrics['emails_sent']}") lines.append(f" Emails Opened: {metrics['emails_opened']} ({metrics['open_rate']}%)") lines.append(f" Links Clicked: {metrics['links_clicked']} ({metrics['click_rate']}%)") lines.append(f" Data Submitted: {metrics['data_submitted']} ({metrics['submit_rate']}%)") lines.append(f" Emails Reported: {metrics['emails_reported']} ({metrics['report_rate']}%)") lines.append(f" Resilience: {metrics['resilience_score']}%") lines.append("") lines.append("[DEPARTMENT BREAKDOWN]") for dept, stats in sorted(metrics.get("department_breakdown", {}).items()): rate = round(stats["clicked"] / max(stats["sent"], 1) * 100, 1) lines.append(f" {dept}: {stats['sent']} sent, {stats['clicked']} clicked ({rate}%)") lines.append("=" * 60) return "\n".join(lines) def main(): parser = argparse.ArgumentParser(description="GoPhish Campaign Automation") subparsers = parser.add_subparsers(dest="command") # Create campaign from config create_parser = subparsers.add_parser("create", help="Create campaign from config") create_parser.add_argument("--config", required=True, help="Campaign config JSON file") # Campaign status status_parser = subparsers.add_parser("status", help="Get campaign status") status_parser.add_argument("--campaign-id", type=int, required=True) # Generate report report_parser = subparsers.add_parser("report", help="Generate campaign report") report_parser.add_argument("--campaign-id", type=int, required=True) report_parser.add_argument("--output", "-o", help="Output file path") report_parser.add_argument("--format", choices=["html", "text", "json"], default="text") # List campaigns subparsers.add_parser("list", help="List all campaigns") # Import users import_parser = subparsers.add_parser("import-users", help="Import user group from CSV") import_parser.add_argument("--csv", required=True, help="CSV file path") import_parser.add_argument("--group-name", required=True, help="Group name") parser.add_argument("--api-url", default=GOPHISH_API_URL) parser.add_argument("--api-key", default=GOPHISH_API_KEY) parser.add_argument("--no-verify-ssl", action="store_true") args = parser.parse_args() if not HAS_REQUESTS: print("Error: 'requests' library required. Install with: pip install requests", file=sys.stderr) sys.exit(1) api_url = args.api_url api_key = args.api_key if not api_key: print("Error: GoPhish API key required. Set GOPHISH_API_KEY env var or use --api-key", file=sys.stderr) sys.exit(1) client = GoPhishClient(api_url, api_key, verify_ssl=not args.no_verify_ssl) if args.command == "create": with open(args.config, "r") as f: config = json.load(f) result = client.create_campaign(**config) print(f"Campaign created: ID={result.get('id')}, Name={result.get('name')}") elif args.command == "status": summary = client.get_campaign_summary(args.campaign_id) print(json.dumps(summary, indent=2)) elif args.command == "report": campaign = client.get_campaign(args.campaign_id) results = client.get_campaign_results(args.campaign_id) metrics = calculate_campaign_metrics(results) if args.format == "html": output = generate_html_report(campaign, metrics) elif args.format == "json": output = json.dumps({"campaign": campaign, "metrics": metrics}, indent=2, default=str) else: output = generate_text_report(campaign, metrics) if args.output: with open(args.output, "w", encoding="utf-8") as f: f.write(output) print(f"Report written to {args.output}") else: print(output) elif args.command == "list": campaigns = client.list_campaigns() for c in campaigns: print(f" ID: {c['id']} | Name: {c['name']} | Status: {c['status']} | " f"Created: {c.get('created_date', '')}") elif args.command == "import-users": result = client.import_group_csv(args.group_name, args.csv) targets = result.get("targets", []) print(f"Group '{args.group_name}' created with {len(targets)} targets") else: parser.print_help() if __name__ == "__main__": main()