#!/usr/bin/env python3 """Splunk SOAR phishing playbook automation via REST API.""" import argparse import email import json import re import time import requests from email import policy from email.parser import BytesParser URL_PATTERN = re.compile(r'https?://[^\s<>"\']+', re.IGNORECASE) IP_PATTERN = re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b') class SOARClient: def __init__(self, base_url: str, token: str, verify_ssl: bool = True): self.base_url = base_url.rstrip("/") self.session = requests.Session() self.session.headers.update({ "ph-auth-token": token, "Content-Type": "application/json", }) self.session.verify = verify_ssl def create_container(self, name: str, description: str, severity: str, label: str = "events") -> dict: payload = { "name": name, "description": description, "severity": severity, "label": label, "status": "new", "sensitivity": "amber", } resp = self.session.post(f"{self.base_url}/rest/container", json=payload, timeout=30) resp.raise_for_status() data = resp.json() return {"container_id": data.get("id"), "success": data.get("success", False)} def add_artifact(self, container_id: int, name: str, cef: dict, label: str = "event", severity: str = "medium", artifact_type: str = "network", run_automation: bool = False) -> dict: payload = { "container_id": container_id, "name": name, "label": label, "severity": severity, "type": artifact_type, "cef": cef, "run_automation": run_automation, } resp = self.session.post(f"{self.base_url}/rest/artifact", json=payload, timeout=30) resp.raise_for_status() data = resp.json() return {"artifact_id": data.get("id"), "success": data.get("success", False)} def trigger_playbook(self, playbook_name: str, container_id: int, scope: str = "new") -> dict: payload = { "container_id": container_id, "playbook_id": playbook_name, "scope": scope, "run": True, } resp = self.session.post(f"{self.base_url}/rest/playbook_run", json=payload, timeout=30) resp.raise_for_status() return resp.json() def get_action_runs(self, container_id: int) -> list: resp = self.session.get( f"{self.base_url}/rest/action_run", params={"_filter_container": container_id, "page_size": 100}, timeout=30, ) resp.raise_for_status() return resp.json().get("data", []) def poll_playbook(self, container_id: int, timeout: int = 300, interval: int = 10) -> list: terminal_states = {"success", "failed", "cancelled"} elapsed = 0 while elapsed < timeout: runs = self.get_action_runs(container_id) if runs and all(r.get("status") in terminal_states for r in runs): return runs time.sleep(interval) elapsed += interval return self.get_action_runs(container_id) def parse_email_file(email_path: str) -> dict: with open(email_path, "rb") as f: msg = BytesParser(policy=policy.default).parse(f) headers = { "from": msg.get("From", ""), "to": msg.get("To", ""), "subject": msg.get("Subject", ""), "reply_to": msg.get("Reply-To", ""), "return_path": msg.get("Return-Path", ""), "message_id": msg.get("Message-ID", ""), "date": msg.get("Date", ""), "x_mailer": msg.get("X-Mailer", ""), } received_headers = msg.get_all("Received", []) auth_results = msg.get("Authentication-Results", "") spf_result = "none" dkim_result = "none" dmarc_result = "none" if "spf=pass" in auth_results.lower(): spf_result = "pass" elif "spf=fail" in auth_results.lower(): spf_result = "fail" if "dkim=pass" in auth_results.lower(): dkim_result = "pass" elif "dkim=fail" in auth_results.lower(): dkim_result = "fail" if "dmarc=pass" in auth_results.lower(): dmarc_result = "pass" elif "dmarc=fail" in auth_results.lower(): dmarc_result = "fail" body_text = "" if msg.is_multipart(): for part in msg.walk(): if part.get_content_type() == "text/plain": body_text += part.get_content() elif part.get_content_type() == "text/html": body_text += part.get_content() else: body_text = msg.get_content() urls = list(set(URL_PATTERN.findall(body_text))) originating_ips = [] for recv in received_headers: originating_ips.extend(IP_PATTERN.findall(recv)) originating_ips = list(set(originating_ips)) return { "headers": headers, "received_count": len(received_headers), "auth": {"spf": spf_result, "dkim": dkim_result, "dmarc": dmarc_result}, "urls": urls, "originating_ips": originating_ips, } def run_phishing_workflow(args) -> dict: email_data = parse_email_file(args.email_file) client = SOARClient(args.soar_url, args.token, verify_ssl=not args.no_verify) sender = email_data["headers"]["from"] subject = email_data["headers"]["subject"] severity = "high" if email_data["auth"]["spf"] == "fail" else "medium" container = client.create_container( name=f"Phishing Report: {subject[:80]}", description=f"Reported phishing email from {sender}", severity=severity, label="phishing", ) cid = container["container_id"] artifacts_created = 0 client.add_artifact(cid, "Email Headers", { "fromAddress": sender, "toAddress": email_data["headers"]["to"], "emailSubject": subject, "emailMessageId": email_data["headers"]["message_id"], "emailReplyTo": email_data["headers"]["reply_to"], "emailReturnPath": email_data["headers"]["return_path"], }, label="email", artifact_type="email", severity=severity) artifacts_created += 1 for ip in email_data["originating_ips"]: client.add_artifact(cid, f"Originating IP: {ip}", { "sourceAddress": ip, }, label="email", artifact_type="ip", severity="medium") artifacts_created += 1 url_list = email_data["urls"] for i, url in enumerate(url_list): is_last = (i == len(url_list) - 1) and not args.playbook client.add_artifact(cid, f"Embedded URL: {url[:60]}", { "requestURL": url, }, label="email", artifact_type="url", severity="high", run_automation=is_last) artifacts_created += 1 playbook_result = None if args.playbook: playbook_result = client.trigger_playbook(args.playbook, cid) action_runs = client.poll_playbook(cid, timeout=args.poll_timeout) playbook_result["action_runs"] = len(action_runs) playbook_result["actions_completed"] = sum( 1 for r in action_runs if r.get("status") == "success" ) return { "incident": { "container_id": cid, "status": "new", "severity": severity, "artifacts_created": artifacts_created, }, "email_analysis": { "sender": sender, "subject": subject, "urls_found": len(url_list), "originating_ips": email_data["originating_ips"], "auth": email_data["auth"], }, "playbook": playbook_result, } def main(): parser = argparse.ArgumentParser(description="SOAR Phishing Playbook Automation") parser.add_argument("--soar-url", required=True, help="Splunk SOAR base URL") parser.add_argument("--token", required=True, help="SOAR API auth token") parser.add_argument("--email-file", required=True, help="Path to .eml phishing email file") parser.add_argument("--playbook", default=None, help="Playbook name or ID to trigger") parser.add_argument("--poll-timeout", type=int, default=300, help="Max seconds to poll for playbook completion") parser.add_argument("--no-verify", action="store_true", help="Disable SSL certificate verification") parser.add_argument("--output", default=None, help="Output JSON file path") args = parser.parse_args() result = run_phishing_workflow(args) report = json.dumps(result, indent=2) if args.output: with open(args.output, "w") as f: f.write(report) print(report) if __name__ == "__main__": main()