Files
Anthropic-Cybersecurity-Skills/skills/detecting-container-escape-attempts/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

169 lines
6.6 KiB
Python

#!/usr/bin/env python3
"""Container escape detection agent using Falco output parsing and audit log analysis.
Monitors for container escape indicators by parsing Falco JSON alerts,
auditd logs, and Docker inspect data for privileged/vulnerable containers.
"""
import argparse
import json
import re
import subprocess
import sys
from datetime import datetime
from pathlib import Path
ESCAPE_VECTORS = {
"nsenter": {"severity": "CRITICAL", "mitre": "T1611", "desc": "Namespace escape via nsenter"},
"unshare": {"severity": "CRITICAL", "mitre": "T1611", "desc": "Namespace manipulation"},
"mount": {"severity": "HIGH", "mitre": "T1611", "desc": "Host filesystem mount"},
"modprobe": {"severity": "CRITICAL", "mitre": "T1611", "desc": "Kernel module loading"},
"insmod": {"severity": "CRITICAL", "mitre": "T1611", "desc": "Kernel module insertion"},
"chroot": {"severity": "HIGH", "mitre": "T1611", "desc": "Chroot escape attempt"},
}
SENSITIVE_PATHS = [
"/var/run/docker.sock", "/proc/sysrq-trigger", "/proc/kcore",
"/proc/kmsg", "/proc/kallsyms", "/sys/kernel",
"/etc/shadow", "/etc/kubernetes/admin.conf",
]
def parse_falco_json(filepath):
alerts = []
with open(filepath, "r") as f:
for line in f:
line = line.strip()
if not line:
continue
try:
evt = json.loads(line)
if any(tag in evt.get("tags", []) for tag in ["escape", "container"]):
alerts.append({
"time": evt.get("time", ""),
"rule": evt.get("rule", ""),
"priority": evt.get("priority", ""),
"output": evt.get("output", ""),
"output_fields": evt.get("output_fields", {}),
})
except json.JSONDecodeError:
continue
return alerts
def parse_auditd_escape_events(filepath):
findings = []
escape_keys = {"container_escape", "container_mount", "kernel_module",
"docker_socket", "process_trace"}
with open(filepath, "r") as f:
for line in f:
for key in escape_keys:
if f'key="{key}"' in line or f"key={key}" in line:
timestamp = re.search(r'msg=audit\((\d+\.\d+):', line)
syscall = re.search(r'syscall=(\w+)', line)
exe = re.search(r'exe="([^"]+)"', line)
findings.append({
"timestamp": timestamp.group(1) if timestamp else "",
"key": key,
"syscall": syscall.group(1) if syscall else "",
"exe": exe.group(1) if exe else "",
"severity": "CRITICAL",
"raw": line.strip()[:200],
})
return findings
def check_privileged_containers():
containers = []
try:
result = subprocess.run(
["docker", "ps", "--format", "{{.ID}} {{.Names}} {{.Image}}"],
capture_output=True, text=True, timeout=10)
if result.returncode != 0:
return containers
for line in result.stdout.strip().split("\n"):
if not line.strip():
continue
parts = line.split(None, 2)
cid = parts[0]
inspect = subprocess.run(
["docker", "inspect", "--format",
"{{.HostConfig.Privileged}} {{.HostConfig.PidMode}} "
"{{range .HostConfig.Binds}}{{.}} {{end}}"],
capture_output=True, text=True, timeout=10)
if inspect.returncode == 0:
info = inspect.stdout.strip()
findings = []
if "true" in info.split()[0:1]:
findings.append("privileged_mode")
if "host" in info:
findings.append("host_pid_namespace")
if "/var/run/docker.sock" in info:
findings.append("docker_socket_mounted")
if findings:
containers.append({
"container_id": cid,
"name": parts[1] if len(parts) > 1 else "",
"image": parts[2] if len(parts) > 2 else "",
"escape_risks": findings,
"severity": "CRITICAL" if "privileged_mode" in findings else "HIGH",
})
except (subprocess.TimeoutExpired, FileNotFoundError):
pass
return containers
def check_dangerous_capabilities(container_id):
dangerous_caps = {"SYS_ADMIN", "SYS_PTRACE", "NET_ADMIN", "SYS_RAWIO",
"SYS_MODULE", "DAC_READ_SEARCH"}
try:
result = subprocess.run(
["docker", "inspect", "--format", "{{.HostConfig.CapAdd}}", container_id],
capture_output=True, text=True, timeout=10)
if result.returncode == 0:
caps = set(re.findall(r'\b([A-Z_]+)\b', result.stdout))
found = caps & dangerous_caps
return [{"capability": c, "severity": "CRITICAL"} for c in found]
except (subprocess.TimeoutExpired, FileNotFoundError):
pass
return []
def main():
parser = argparse.ArgumentParser(description="Container Escape Detector")
parser.add_argument("--falco-log", help="Path to Falco JSON output log")
parser.add_argument("--audit-log", help="Path to auditd log file")
parser.add_argument("--check-containers", action="store_true",
help="Check running containers for escape risks")
parser.add_argument("--container-id", help="Check specific container capabilities")
args = parser.parse_args()
results = {"timestamp": datetime.utcnow().isoformat() + "Z", "findings": []}
if args.falco_log:
alerts = parse_falco_json(args.falco_log)
results["falco_alerts"] = alerts
results["findings"].extend([{"source": "falco", **a} for a in alerts])
if args.audit_log:
audit = parse_auditd_escape_events(args.audit_log)
results["audit_events"] = audit
results["findings"].extend([{"source": "auditd", **a} for a in audit])
if args.check_containers:
priv = check_privileged_containers()
results["privileged_containers"] = priv
results["findings"].extend([{"source": "docker_inspect", **c} for c in priv])
if args.container_id:
caps = check_dangerous_capabilities(args.container_id)
results["dangerous_capabilities"] = caps
results["findings"].extend([{"source": "capabilities", **c} for c in caps])
results["total_findings"] = len(results["findings"])
print(json.dumps(results, indent=2))
if __name__ == "__main__":
main()