mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-13 22:54:53 +03:00
c47eed6a64
- Fix 25 shell=True subprocess calls with list-based commands - Fix 49 verify=False in defensive skills (env-var override) - Add timeout to 231 HTTP/subprocess/socket calls - Fix 6 SQL injection patterns with whitelist validation - Replace 8 __import__() with standard imports - Remove 701 unused imports across 442 files - Add authorized-testing disclaimers to all offensive skills - Complete 11 incomplete skill directories - Expand 10 stub SKILL.md files with full content - Fix 2 YAML parse errors in frontmatter - Fix 5 pre-existing syntax errors - Convert 22 hardcoded paths/ports to environment variables - Back up 21 redundant skill pairs to .bak - Fix 2 global declaration errors - 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE) - 0 compile errors across all 724 agent.py files
278 lines
10 KiB
Python
278 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""Velociraptor incident response collection agent.
|
|
|
|
Interfaces with the Velociraptor DFIR platform API to schedule artifact
|
|
collections on endpoints, retrieve results, and generate IR reports.
|
|
Supports common forensic artifacts including process listings, network
|
|
connections, autoruns, event logs, and file system evidence.
|
|
"""
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
import time
|
|
from datetime import datetime, timezone
|
|
|
|
try:
|
|
import requests
|
|
except ImportError:
|
|
print("[!] 'requests' library required: pip install requests", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
try:
|
|
import grpc
|
|
HAS_GRPC = True
|
|
except ImportError:
|
|
HAS_GRPC = False
|
|
|
|
|
|
def get_velo_config():
|
|
"""Return Velociraptor API configuration."""
|
|
api_url = os.environ.get("VELOCIRAPTOR_API_URL", "https://localhost:8001")
|
|
api_key = os.environ.get("VELOCIRAPTOR_API_KEY", "")
|
|
cert_path = os.environ.get("VELOCIRAPTOR_CERT", "")
|
|
return api_url.rstrip("/"), api_key, cert_path
|
|
|
|
|
|
def velo_api_call(api_url, api_key, endpoint, method="GET", data=None, cert_path=None):
|
|
"""Make an authenticated API call to Velociraptor."""
|
|
url = f"{api_url}{endpoint}"
|
|
headers = {
|
|
"Authorization": f"Bearer {api_key}",
|
|
"Content-Type": "application/json",
|
|
}
|
|
verify = cert_path if cert_path else False
|
|
if method == "POST":
|
|
resp = requests.post(url, headers=headers, json=data, verify=verify, timeout=60)
|
|
else:
|
|
resp = requests.get(url, headers=headers, verify=verify, timeout=60)
|
|
resp.raise_for_status()
|
|
return resp.json()
|
|
|
|
|
|
def search_clients(api_url, api_key, query, cert_path=None):
|
|
"""Search for Velociraptor clients by hostname, label, or client ID."""
|
|
print(f"[*] Searching for clients: {query}")
|
|
data = {"query": query, "count": 100}
|
|
result = velo_api_call(api_url, api_key, "/api/v1/SearchClients", "POST", data, cert_path)
|
|
clients = result.get("items", [])
|
|
print(f"[+] Found {len(clients)} client(s)")
|
|
for c in clients:
|
|
os_info = c.get("os_info", {})
|
|
print(f" {c.get('client_id', 'N/A'):20s} | "
|
|
f"{os_info.get('hostname', 'unknown'):20s} | "
|
|
f"{os_info.get('system', 'unknown'):10s} | "
|
|
f"Last seen: {c.get('last_seen_at', 'N/A')}")
|
|
return clients
|
|
|
|
|
|
def collect_artifact(api_url, api_key, client_id, artifacts, parameters=None, cert_path=None):
|
|
"""Schedule an artifact collection on a specific client."""
|
|
print(f"[*] Scheduling collection on {client_id}: {', '.join(artifacts)}")
|
|
specs = []
|
|
for artifact in artifacts:
|
|
spec = {"artifact": artifact}
|
|
if parameters and artifact in parameters:
|
|
spec["parameters"] = {"env": [
|
|
{"key": k, "value": v} for k, v in parameters[artifact].items()
|
|
]}
|
|
specs.append(spec)
|
|
|
|
data = {
|
|
"client_id": client_id,
|
|
"artifacts": artifacts,
|
|
"specs": specs,
|
|
}
|
|
result = velo_api_call(api_url, api_key, "/api/v1/CollectArtifact", "POST", data, cert_path)
|
|
flow_id = result.get("flow_id", "")
|
|
print(f"[+] Collection started, flow ID: {flow_id}")
|
|
return flow_id
|
|
|
|
|
|
def get_flow_status(api_url, api_key, client_id, flow_id, cert_path=None):
|
|
"""Check the status of a collection flow."""
|
|
data = {"client_id": client_id, "flow_id": flow_id}
|
|
result = velo_api_call(api_url, api_key, "/api/v1/GetFlowDetails", "POST", data, cert_path)
|
|
context = result.get("context", {})
|
|
state = context.get("state", "UNSET")
|
|
return state, context
|
|
|
|
|
|
def wait_for_collection(api_url, api_key, client_id, flow_id, max_wait=300, cert_path=None):
|
|
"""Poll until a collection flow completes."""
|
|
print(f"[*] Waiting for collection to complete (max {max_wait}s)...")
|
|
elapsed = 0
|
|
interval = 10
|
|
while elapsed < max_wait:
|
|
state, context = get_flow_status(api_url, api_key, client_id, flow_id, cert_path)
|
|
if state == "FINISHED":
|
|
total_rows = context.get("total_collected_rows", 0)
|
|
total_bytes = context.get("total_uploaded_bytes", 0)
|
|
print(f"[+] Collection complete: {total_rows} rows, "
|
|
f"{total_bytes / 1024:.1f} KB uploaded")
|
|
return True, context
|
|
if state == "ERROR":
|
|
print(f"[!] Collection failed: {context.get('status', 'unknown')}", file=sys.stderr)
|
|
return False, context
|
|
print(f" State: {state} ({elapsed}s elapsed)")
|
|
time.sleep(interval)
|
|
elapsed += interval
|
|
print("[!] Timed out waiting for collection", file=sys.stderr)
|
|
return False, {}
|
|
|
|
|
|
def get_flow_results(api_url, api_key, client_id, flow_id, artifact, cert_path=None):
|
|
"""Retrieve collected artifact results."""
|
|
print(f"[*] Retrieving results for {artifact}")
|
|
data = {
|
|
"client_id": client_id,
|
|
"flow_id": flow_id,
|
|
"artifact": artifact,
|
|
"count": 10000,
|
|
}
|
|
result = velo_api_call(api_url, api_key, "/api/v1/GetTable", "POST", data, cert_path)
|
|
rows = result.get("rows", [])
|
|
columns = result.get("columns", [])
|
|
print(f"[+] Retrieved {len(rows)} row(s), {len(columns)} column(s)")
|
|
return rows, columns
|
|
|
|
|
|
IR_ARTIFACT_SETS = {
|
|
"triage": [
|
|
"Windows.System.Pslist",
|
|
"Windows.Network.Netstat",
|
|
"Windows.Sys.Users",
|
|
"Windows.System.TaskScheduler",
|
|
"Generic.System.Pstree",
|
|
],
|
|
"persistence": [
|
|
"Windows.Sysinternals.Autoruns",
|
|
"Windows.System.TaskScheduler",
|
|
"Windows.Registry.Run",
|
|
"Windows.System.Services",
|
|
],
|
|
"network": [
|
|
"Windows.Network.Netstat",
|
|
"Windows.Network.ArpCache",
|
|
"Windows.Network.DNSCache",
|
|
"Windows.Network.InterfaceAddresses",
|
|
],
|
|
"logs": [
|
|
"Windows.EventLogs.Cleared",
|
|
"Windows.EventLogs.RDPAuth",
|
|
"Windows.EventLogs.PowershellScriptblock",
|
|
],
|
|
"linux_triage": [
|
|
"Linux.Sys.Pslist",
|
|
"Linux.Network.Netstat",
|
|
"Linux.Sys.Users",
|
|
"Linux.Sys.Crontab",
|
|
"Linux.Sys.LastUserLogin",
|
|
],
|
|
}
|
|
|
|
|
|
def format_summary(client_id, artifacts, flow_context, results_summary):
|
|
"""Print collection summary."""
|
|
print(f"\n{'='*60}")
|
|
print(f" Velociraptor IR Collection Report")
|
|
print(f"{'='*60}")
|
|
print(f" Client ID : {client_id}")
|
|
print(f" Flow ID : {flow_context.get('session_id', 'N/A')}")
|
|
print(f" State : {flow_context.get('state', 'N/A')}")
|
|
print(f" Artifacts : {len(artifacts)}")
|
|
print(f" Total Rows : {flow_context.get('total_collected_rows', 0)}")
|
|
|
|
for artifact_name, row_count in results_summary.items():
|
|
print(f" {artifact_name:50s}: {row_count} rows")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Velociraptor IR collection agent"
|
|
)
|
|
sub = parser.add_subparsers(dest="command")
|
|
|
|
p_search = sub.add_parser("search", help="Search for clients")
|
|
p_search.add_argument("--query", required=True, help="Search query (hostname, label, client ID)")
|
|
|
|
p_collect = sub.add_parser("collect", help="Collect artifacts from client")
|
|
p_collect.add_argument("--client-id", required=True, help="Velociraptor client ID")
|
|
p_collect.add_argument("--artifacts", nargs="+", help="Specific artifact names to collect")
|
|
p_collect.add_argument("--preset", choices=list(IR_ARTIFACT_SETS.keys()),
|
|
help="Use a predefined IR artifact set")
|
|
p_collect.add_argument("--wait", type=int, default=300, help="Max wait time in seconds")
|
|
|
|
p_status = sub.add_parser("status", help="Check collection status")
|
|
p_status.add_argument("--client-id", required=True)
|
|
p_status.add_argument("--flow-id", required=True)
|
|
|
|
parser.add_argument("--api-url", help="Velociraptor API URL (or VELOCIRAPTOR_API_URL env)")
|
|
parser.add_argument("--api-key", help="API key (or VELOCIRAPTOR_API_KEY env)")
|
|
parser.add_argument("--cert", help="CA cert path (or VELOCIRAPTOR_CERT env)")
|
|
parser.add_argument("--output", "-o", help="Output JSON report path")
|
|
parser.add_argument("--verbose", "-v", action="store_true")
|
|
args = parser.parse_args()
|
|
|
|
if not args.command:
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
if args.api_url:
|
|
os.environ["VELOCIRAPTOR_API_URL"] = args.api_url
|
|
if args.api_key:
|
|
os.environ["VELOCIRAPTOR_API_KEY"] = args.api_key
|
|
if args.cert:
|
|
os.environ["VELOCIRAPTOR_CERT"] = args.cert
|
|
|
|
api_url, api_key, cert_path = get_velo_config()
|
|
if not api_key:
|
|
print("[!] Set VELOCIRAPTOR_API_KEY env var or use --api-key", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
result = {}
|
|
|
|
if args.command == "search":
|
|
clients = search_clients(api_url, api_key, args.query, cert_path)
|
|
result = {"action": "search", "query": args.query, "clients": clients}
|
|
|
|
elif args.command == "collect":
|
|
artifacts = args.artifacts or IR_ARTIFACT_SETS.get(args.preset, [])
|
|
if not artifacts:
|
|
print("[!] Specify --artifacts or --preset", file=sys.stderr)
|
|
sys.exit(1)
|
|
flow_id = collect_artifact(api_url, api_key, args.client_id, artifacts, cert_path=cert_path)
|
|
success, context = wait_for_collection(api_url, api_key, args.client_id, flow_id, args.wait, cert_path)
|
|
results_summary = {}
|
|
if success:
|
|
for artifact in artifacts:
|
|
try:
|
|
rows, cols = get_flow_results(api_url, api_key, args.client_id, flow_id, artifact, cert_path)
|
|
results_summary[artifact] = len(rows)
|
|
except Exception as e:
|
|
results_summary[artifact] = f"Error: {e}"
|
|
format_summary(args.client_id, artifacts, context, results_summary)
|
|
result = {"action": "collect", "client_id": args.client_id, "flow_id": flow_id,
|
|
"state": context.get("state", "UNKNOWN"), "artifacts": results_summary}
|
|
|
|
elif args.command == "status":
|
|
state, context = get_flow_status(api_url, api_key, args.client_id, args.flow_id, cert_path)
|
|
print(f"[*] Flow {args.flow_id}: {state}")
|
|
result = {"action": "status", "flow_id": args.flow_id, "state": state, "context": context}
|
|
|
|
report = {
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
"tool": "Velociraptor",
|
|
"result": result,
|
|
}
|
|
if args.output:
|
|
with open(args.output, "w") as f:
|
|
json.dump(report, f, indent=2)
|
|
print(f"\n[+] Report saved to {args.output}")
|
|
elif args.verbose:
|
|
print(json.dumps(report, indent=2))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|