mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-30 16:06:52 +03:00
Production hardening: security fixes, code quality, 724 skills complete
- 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
This commit is contained in:
@@ -1,61 +1,277 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Velociraptor IR collection audit."""
|
||||
import argparse, json, sys
|
||||
"""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:
|
||||
requests = None
|
||||
print("[!] 'requests' library required: pip install requests", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def audit_config(target, token):
|
||||
findings = []
|
||||
if not requests: return [{"error": "requests required"}]
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
try:
|
||||
resp = requests.get(f"{target}/api/v1/status", headers=headers, timeout=10)
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
if not data.get("enabled", True):
|
||||
findings.append({"check": "Service Status", "status": "DISABLED", "severity": "CRITICAL"})
|
||||
elif resp.status_code == 401:
|
||||
findings.append({"check": "Authentication", "status": "UNAUTHORIZED", "severity": "HIGH"})
|
||||
except requests.RequestException as e:
|
||||
findings.append({"error": str(e)})
|
||||
return findings
|
||||
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 check_compliance(target, token):
|
||||
findings = []
|
||||
if not requests: return []
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
try:
|
||||
resp = requests.get(f"{target}/api/v1/compliance", headers=headers, timeout=10)
|
||||
if resp.status_code == 200:
|
||||
for item in resp.json().get("checks", []):
|
||||
if item.get("status") != "PASS":
|
||||
findings.append({"check": item.get("name"), "status": item.get("status"),
|
||||
"severity": item.get("severity", "MEDIUM")})
|
||||
except requests.RequestException:
|
||||
pass
|
||||
return findings
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description="Velociraptor IR collection audit")
|
||||
p.add_argument("--target", required=True, help="Target URL")
|
||||
p.add_argument("--token", required=True, help="API token")
|
||||
p.add_argument("--output", "-o", help="Output JSON report")
|
||||
p.add_argument("--verbose", "-v", action="store_true")
|
||||
a = p.parse_args()
|
||||
print("[*] Velociraptor IR collection audit")
|
||||
report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []}
|
||||
report["findings"].extend(audit_config(a.target, a.token))
|
||||
report["findings"].extend(check_compliance(a.target, a.token))
|
||||
high = sum(1 for f in report["findings"] if f.get("severity") in ("HIGH", "CRITICAL"))
|
||||
report["risk_level"] = "HIGH" if high else "MEDIUM" if report["findings"] else "LOW"
|
||||
print(f"[*] {len(report['findings'])} findings, risk: {report['risk_level']}")
|
||||
if a.output:
|
||||
with open(a.output, "w") as f: json.dump(report, f, indent=2)
|
||||
else:
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user