Files
Anthropic-Cybersecurity-Skills/skills/correlating-security-events-in-qradar/scripts/agent.py
T
mukul975 27c6414ca5 Add folder anatomy (scripts/agent.py + references/api-reference.md) for 648 cybersecurity skills
Complete skill folder anatomy across all cybersecurity skills:
- scripts/agent.py: 80-150 line Python agents using real libraries (impacket,
  boto3, azure-mgmt-*, kubernetes, pefile, yara, scapy, shodan, stix2, etc.)
- references/api-reference.md: real API documentation with method signatures
- LICENSE: MIT license for all skill folders
2026-03-10 21:02:12 +01:00

174 lines
6.5 KiB
Python

#!/usr/bin/env python3
"""IBM QRadar SIEM correlation and offense management agent."""
import json
import sys
import urllib.request
import urllib.parse
import ssl
from datetime import datetime
class QRadarClient:
"""Client for QRadar REST API operations."""
def __init__(self, host, api_token, verify_ssl=False):
self.base_url = f"https://{host}/api"
self.headers = {
"SEC": api_token,
"Content-Type": "application/json",
"Accept": "application/json",
}
self.ctx = ssl.create_default_context()
if not verify_ssl:
self.ctx.check_hostname = False
self.ctx.verify_mode = ssl.CERT_NONE
def _request(self, method, endpoint, data=None):
url = f"{self.base_url}/{endpoint}"
body = json.dumps(data).encode() if data else None
req = urllib.request.Request(url, data=body, headers=self.headers, method=method)
try:
with urllib.request.urlopen(req, context=self.ctx, timeout=60) as resp:
return json.loads(resp.read().decode())
except urllib.error.HTTPError as e:
return {"error": e.code, "message": e.read().decode()}
except Exception as e:
return {"error": str(e)}
def search_aql(self, query):
"""Execute an AQL query and return results."""
encoded = urllib.parse.quote(query)
result = self._request("POST", f"ariel/searches?query_expression={encoded}")
if "error" in result:
return result
search_id = result.get("search_id")
if not search_id:
return result
import time
for _ in range(30):
status = self._request("GET", f"ariel/searches/{search_id}")
if status.get("status") == "COMPLETED":
return self._request("GET", f"ariel/searches/{search_id}/results")
time.sleep(2)
return {"error": "AQL query timed out"}
def get_offenses(self, status_filter="OPEN", limit=50):
"""Retrieve offenses filtered by status."""
params = f"?filter=status%3D%22{status_filter}%22&Range=items%3D0-{limit-1}"
return self._request("GET", f"siem/offenses{params}")
def get_offense_details(self, offense_id):
"""Get detailed information about a specific offense."""
return self._request("GET", f"siem/offenses/{offense_id}")
def close_offense(self, offense_id, closing_reason_id, note="Closed by automation"):
"""Close an offense with a reason and note."""
params = f"?closing_reason_id={closing_reason_id}&status=CLOSED"
result = self._request("POST", f"siem/offenses/{offense_id}{params}")
if "error" not in result:
self.add_note(offense_id, note)
return result
def add_note(self, offense_id, note_text):
"""Add an investigation note to an offense."""
data = {"note_text": note_text}
return self._request("POST", f"siem/offenses/{offense_id}/notes", data)
def get_reference_set(self, name):
"""Retrieve a reference set and its entries."""
encoded = urllib.parse.quote(name)
return self._request("GET", f"reference_data/sets/{encoded}")
def add_to_reference_set(self, name, value):
"""Add a value to a reference set."""
encoded = urllib.parse.quote(name)
return self._request("POST", f"reference_data/sets/{encoded}?value={urllib.parse.quote(value)}")
def create_reference_set(self, name, element_type="IP", ttl="30 days"):
"""Create a new reference set."""
data = {
"name": name,
"element_type": element_type,
"timeout_type": "LAST_SEEN",
"time_to_live": ttl,
}
return self._request("POST", "reference_data/sets", data)
def get_rules(self, limit=50):
"""List custom rules."""
return self._request("GET", f"analytics/rules?Range=items%3D0-{limit-1}")
def brute_force_aql(client, hours=24):
"""AQL query to detect brute force followed by success."""
query = f"""
SELECT sourceIP, destinationIP, username,
QIDNAME(qid) AS event_name, COUNT(*) as cnt
FROM events
WHERE category = 'Authentication'
AND QIDNAME(qid) ILIKE '%fail%'
AND startTime > NOW() - {hours}*60*60*1000
GROUP BY sourceIP, destinationIP, username, qid
HAVING COUNT(*) > 10
ORDER BY cnt DESC
LIMIT 50
"""
return client.search_aql(query)
def lateral_movement_aql(client, hours=24):
"""AQL query to detect lateral movement patterns."""
query = f"""
SELECT sourceIP, COUNT(DISTINCT destinationIP) as dest_count,
COUNT(*) as event_count, username
FROM events
WHERE category = 'Authentication'
AND sourceIP NOT IN (SELECT ip FROM reference_data.sets('Domain_Controllers'))
AND startTime > NOW() - {hours}*60*60*1000
GROUP BY sourceIP, username
HAVING COUNT(DISTINCT destinationIP) > 5
ORDER BY dest_count DESC
LIMIT 30
"""
return client.search_aql(query)
def generate_report(client):
"""Generate a QRadar offense summary report."""
offenses = client.get_offenses("OPEN", 100)
if isinstance(offenses, dict) and "error" in offenses:
return offenses
return {
"timestamp": datetime.utcnow().isoformat() + "Z",
"open_offenses": len(offenses) if isinstance(offenses, list) else 0,
"offenses": offenses[:20] if isinstance(offenses, list) else offenses,
}
if __name__ == "__main__":
import os
host = os.environ.get("QRADAR_HOST", "qradar.example.com")
token = os.environ.get("QRADAR_TOKEN", "")
if not token:
print("Set QRADAR_HOST and QRADAR_TOKEN environment variables")
sys.exit(1)
client = QRadarClient(host, token)
action = sys.argv[1] if len(sys.argv) > 1 else "report"
if action == "report":
print(json.dumps(generate_report(client), indent=2))
elif action == "offenses":
print(json.dumps(client.get_offenses(), indent=2))
elif action == "offense" and len(sys.argv) > 2:
print(json.dumps(client.get_offense_details(sys.argv[2]), indent=2))
elif action == "brute-force":
print(json.dumps(brute_force_aql(client), indent=2))
elif action == "lateral-movement":
print(json.dumps(lateral_movement_aql(client), indent=2))
elif action == "aql" and len(sys.argv) > 2:
print(json.dumps(client.search_aql(" ".join(sys.argv[2:])), indent=2))
else:
print("Usage: agent.py [report|offenses|offense <id>|brute-force|lateral-movement|aql <query>]")